Ble蓝牙App(三)特性使用
- 前言
- 正文
- 一、获取属性列表
- 二、属性适配器
- 三、获取特性名称
- 四、特性适配器
- 五、加载特性
- 六、显示特性和属性
- 七、源码
前言
在上一篇中我们完成了连接和发现服务两个动作,那么再发现服务之后要做什么呢?发现服务只是让你知道设备有什么服务,可以做什么事情。
正文
本篇要做的是显示服务下的特性,首先我们了解一下特性的基本知识。在蓝牙低功耗(BLE)中,特性(Characteristic)
是蓝牙设备提供的一种数据单元,用于描述设备的某个属性或功能。特性包含了一系列的属性和值,可以用于读取、写入和通知数据。
BLE特性相关的关键概念和说明:
UUID(Universally Unique Identifier)
:每个特性都会有一个唯一的UUID,用于标识该特性。值(Value)
:特性包含一个值,可以是字节数组、字符串或其他数据类型。该值代表特性的当前状态或数据内容。属性(Properties)
:特性具有一组属性,包括读、写、通知等。属性决定了可以对特性进行哪些操作。读(Read)
:允许外部设备从特性中读取当前的值。写(Write)
:允许外部设备向特性写入一个新的值。通知(Notify)
:当特性的值发生变化时,可以通过通知方式将新的值发送给订阅该特性的外部设备。描述符(Descriptor)
:特性可以附带一个或多个描述符,用于提供关于特性的额外信息或配置。
使用BLE特性,可以实现各种功能和数据交互,例如传感器数据的读取、设备状态的监控、远程控制等。特性的读写和通知操作可以通过与蓝牙设备的交互来实现。需要注意的是,BLE特性的操作和功能是由设备的厂商定义的,并在设备的GATT(Generic Attribute Profile)配置文件中进行描述。
首先理清一下思路,我们现在知道服务下面有特性,特性下面有一些属性值,其中属性(Properties)
尤为重要,因为它决定了你的特性可以进行那些操作。用一个图来说明服务,特性,属性之间的关系。
一、获取属性列表
下面我们先获取最下面的属性,这是一个列表,属性值的处理有一些不同,首先我们在BleUtils中增加一个函数,代码如下所示:
/*** 获取属性*/fun getProperties(property: Int): List<String> {val properties: MutableList<String> = ArrayList()for (i in 0..7) {when (property and (1 shl i)) {0x01 -> properties.add("Broadcast")0x02 -> properties.add("Read")0x04 -> properties.add("Write No Response")0x08 -> properties.add("Write")0x10 -> properties.add("Notify")0x20 -> properties.add("Indicate")0x40 -> properties.add("Authenticated Signed Writes")0x80 -> properties.add("Extended Properties")}}return properties}
这里是通过位运算进行计算属性的值,首先是循环遍历,shl
是一种位运算符,用于执行按位左移操作。shl 是 “shift left” 的缩写。and
用于执行按位与操作。先左移再按位与,得到最终的值,根据值得到属性描述,这些描述就是具体的功能操作。会返回一个属性列表,有了列表我们就可以写一个适配器了。
二、属性适配器
首先我们在layout下创建一个item_property.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/tv_property"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="8dp"android:text="property"android:textColor="@color/orange" />
因为是String类型,所以我们就直接用一个TextView显示即可,下面我们写适配器,在adapter包下新建一个PropertyAdapter
类,代码如下所示:
class PropertyAdapter(private val properties: List<String>,private val listener: OnItemClickListener
) : RecyclerView.Adapter<PropertyAdapter.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {return ViewHolder(ItemPropertyBinding.inflate(LayoutInflater.from(parent.context), parent, false)).apply {binding.tvProperty.setOnClickListener { v -> listener.onItemClick(v, adapterPosition) }}}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.binding.tvProperty.text = properties[position]}override fun getItemCount() = properties.sizeclass ViewHolder(itemView: ItemPropertyBinding) : RecyclerView.ViewHolder(itemView.root) {var binding: ItemPropertyBindinginit {binding = itemView}}
}
这里进行了属性的点击监听,我们可以回调到特性适配器中去处理,下面我们要处理的就是特性了。
三、获取特性名称
首先是特性名称,同样是根据UUID,同样是那个PDF文档,在BleUtils中增加一个getCharacteristicsName()
函数,代码有点多,如下所示:
/*** 获取特性名称* @param uuid UUID*/fun getCharacteristicsName(uuid: UUID) =when ("0x${uuid.toString().substring(4, 8).uppercase(Locale.getDefault())}") {"0x2A00" -> "Device Name""0x2A01" -> "Appearance""0x2A02" -> "Peripheral Privacy Flag""0x2A03" -> "Reconnection Address""0x2A04" -> "Peripheral Preferred Connection Parameters""0x2A05" -> "Service Changed""0x2A06" -> "Alert Level""0x2A07" -> "Tx Power Level""0x2A08" -> "Date Time""0x2A09" -> "Day of Week""0x2A0A" -> "Day Date Time""0x2A0C" -> "Exact Time 256""0x2A0D" -> "DST Offset""0x2A0E" -> "Time Zone""0x2A0F" -> "Local Time Information""0x2A11" -> "Time with DST""0x2A12" -> "Time Accuracy""0x2A13" -> "Time Source""0x2A14" -> "Reference Time Information""0x2A16" -> "Time Update Control Point""0x2A17" -> "Time Update State""0x2A18" -> "Glucose Measurement""0x2A19" -> "Battery Level""0x2A1C" -> "Temperature Measurement""0x2A1D" -> "Temperature Type""0x2A1E" -> "Intermediate Temperature""0x2A21" -> "Measurement Interval""0x2A22" -> "Boot Keyboard Input Report""0x2A23" -> "System ID""0x2A24" -> "Model Number String""0x2A25" -> "Serial Number String""0x2A26" -> "Firmware Revision String""0x2A27" -> "Hardware Revision String""0x2A28" -> "Software Revision String""0x2A29" -> "Manufacturer Name String""0x2A2A" -> "IEEE 11073-20601 Regulatory Certification Data List""0x2A2B" -> "Current Time""0x2A2C" -> "Magnetic Declination""0x2A31" -> "Scan Refresh""0x2A32" -> "Boot Keyboard Output Report""0x2A33" -> "Boot Mouse Input Report""0x2A34" -> "Glucose Measurement Context""0x2A35" -> "Blood Pressure Measurement""0x2A36" -> "Intermediate Cuff Pressure""0x2A37" -> "Heart Rate Measurement""0x2A38" -> "Body Sensor Location""0x2A39" -> "Heart Rate Control Point""0x2A3F" -> "Alert Status""0x2A40" -> "Ringer Control Point""0x2A41" -> "Ringer Setting""0x2A42" -> "Alert Category ID Bit Mask""0x2A43" -> "Alert Category ID""0x2A44" -> "Alert Notification Control Point""0x2A45" -> "Unread Alert Status""0x2A46" -> "New Alert""0x2A47" -> "Supported New Alert Category""0x2A48" -> "Supported Unread Alert Category""0x2A49" -> "Blood Pressure Feature""0x2A4A" -> "HID Information""0x2A4B" -> "Report Map""0x2A4C" -> "HID Control Point""0x2A4D" -> "Report""0x2A4E" -> "Protocol Mode""0x2A4F" -> "Scan Interval Window""0x2A50" -> "PnP ID""0x2A51" -> "Glucose Feature""0x2A52" -> "Record Access Control Point""0x2A53" -> "RSC Measurement""0x2A54" -> "RSC Feature""0x2A55" -> "SC Control Point""0x2A5A" -> "Aggregate""0x2A5B" -> "CSC Measurement""0x2A5C" -> "CSC Feature""0x2A5D" -> "Sensor Location""0x2A5E" -> "PLX Spot-Check Measurement""0x2A5F" -> "PLX Continuous Measurement""0x2A60" -> "PLX Features""0x2A63" -> "Cycling Power Measurement""0x2A64" -> "Cycling Power Vector""0x2A65" -> "Cycling Power Feature""0x2A66" -> "Cycling Power Control Point""0x2A67" -> "Location and Speed""0x2A68" -> "Navigation""0x2A69" -> "Position Quality""0x2A6A" -> "LN Feature""0x2A6B" -> "LN Control Point""0x2A6C" -> "Elevation""0x2A6D" -> "Pressure""0x2A6E" -> "Temperature""0x2A6F" -> "Humidity""0x2A70" -> "True Wind Speed""0x2A71" -> "True Wind Direction""0x2A72" -> "Apparent Wind Speed""0x2A73" -> "Apparent Wind Direction""0x2A74" -> "Gust Factor""0x2A75" -> "Pollen Concentration""0x2A76" -> "UV Index""0x2A77" -> "Irradiance""0x2A78" -> "Rainfall""0x2A79" -> "Wind Chill""0x2A7A" -> "Heat Index""0x2A7B" -> "Dew Point""0x2A7D" -> "Descriptor Value Changed""0x2A7E" -> "Aerobic Heart Rate Lower Limit""0x2A7F" -> "Aerobic Threshold""0x2A80" -> "Age""0x2A81" -> "Anaerobic Heart Rate Lower Limit""0x2A82" -> "Anaerobic Heart Rate Upper Limit""0x2A83" -> "Anaerobic Threshold""0x2A84" -> "Aerobic Heart Rate Upper Limit""0x2A85" -> "Date of Birth""0x2A86" -> "Date of Threshold Assessment""0x2A87" -> "Email Address""0x2A88" -> "Fat Burn Heart Rate Lower Limit""0x2A89" -> "Fat Burn Heart Rate Upper Limit""0x2A8A" -> "First Name""0x2A8B" -> "Five Zone Heart Rate Limits""0x2A8C" -> "Gender""0x2A8D" -> "Heart Rate Max""0x2A8E" -> "Height""0x2A8F" -> "Hip Circumference""0x2A90" -> "Last Name""0x2A91" -> "Maximum Recommended Heart Rate""0x2A92" -> "Resting Heart Rate""0x2A93" -> "Sport Type for Aerobic and Anaerobic Thresholds""0x2A94" -> "Three Zone Heart Rate Limits""0x2A95" -> "Two Zone Heart Rate Limits""0x2A96" -> "VO2 Max""0x2A97" -> "Waist Circumference""0x2A98" -> "Weight""0x2A99" -> "Database Change Increment""0x2A9A" -> "User Index""0x2A9B" -> "Body Composition Feature""0x2A9C" -> "Body Composition Measurement""0x2A9D" -> "Weight Measurement""0x2A9E" -> "Weight Scale Feature""0x2A9F" -> "User Control Point""0x2AA0" -> "Magnetic Flux Density - 2D""0x2AA1" -> "Magnetic Flux Density - 3D""0x2AA2" -> "Language""0x2AA3" -> "Barometric Pressure Trend""0x2AA4" -> "Bond Management Control Point""0x2AA5" -> "Bond Management Feature""0x2AA6" -> "Central Address Resolution""0x2AA7" -> "CGM Measurement""0x2AA8" -> "CGM Feature""0x2AA9" -> "CGM Status""0x2AAA" -> "CGM Session Start Time""0x2AAB" -> "CGM Session Run Time""0x2AAC" -> "CGM Specific Ops Control Point""0x2AAD" -> "Indoor Positioning Configuration""0x2AAE" -> "Latitude""0x2AAF" -> "Longitude""0x2AB0" -> "Local North Coordinate""0x2AB1" -> "Local East Coordinate""0x2AB2" -> "Floor Number""0x2AB3" -> "Altitude""0x2AB4" -> "Uncertainty""0x2AB5" -> "Location Name""0x2AB6" -> "URI""0x2AB7" -> "HTTP Headers""0x2AB8" -> "HTTP Status Code""0x2AB9" -> "HTTP Entity Body""0x2ABA" -> "HTTP Control Point""0x2ABB" -> "HTTPS Security""0x2ABC" -> "TDS Control Point""0x2ABD" -> "OTS Feature""0x2ABE" -> "Object Name""0x2ABF" -> "Object Type""0x2AC0" -> "Object Size""0x2AC1" -> "Object First -Created""0x2AC2" -> "Object Last - Modified""0x2AC3" -> "Object ID""0x2AC4" -> "Object Properties""0x2AC5" -> "Object Action Control Point""0x2AC6" -> "Object List Control Point""0x2AC7" -> "Object List Filter""0x2AC8" -> "Object Changed""0x2AC9" -> "Resolvable Private Address Only""0x2ACC" -> "Fitness Machine Feature""0x2ACD" -> "Treadmill Data""0x2ACE" -> "Cross Trainer Data""0x2ACF" -> "Step Climber Data""0x2AD0" -> "Stair Climber Data""0x2AD1" -> "Rower Data""0x2AD2" -> "Indoor Bike Data""0x2AD3" -> "Training Status""0x2AD4" -> "Supported Speed Range""0x2AD5" -> "Supported Inclination Range""0x2AD6" -> "Supported Resistance Level Range""0x2AD7" -> "Supported Heart Rate Range""0x2AD8" -> "Supported Power Range""0x2AD9" -> "Fitness Machine Control Point""0x2ADA" -> "Fitness Machine Status""0x2ADB" -> "Mesh Provisioning Data In""0x2ADC" -> "Mesh Provisioning Data Out""0x2ADD" -> "Mesh Proxy Data In""0x2ADE" -> "Mesh Proxy Data Out""0x2AE0" -> "Average Current""0x2AE1" -> "Average Voltage""0x2AE2" -> "Boolean""0x2AE3" -> "Chromatic Distance from Planckian""0x2AE4" -> "Chromaticity Coordinates""0x2AE5" -> "Chromaticity in CCT and Duv Values""0x2AE6" -> "Chromaticity Tolerance""0x2AE7" -> "CIE 13.3 - 1995 Color Rendering Index""0x2AE8" -> "Coefficient""0x2AE9" -> "Correlated Color Temperature""0x2AEA" -> "Count 16""0x2AEB" -> "Count 24""0x2AEC" -> "Country Code""0x2AED" -> "Date UTC""0x2AEE" -> "Electric Current""0x2AEF" -> "Electric Current Range""0x2AF0" -> "Electric Current Specification""0x2AF1" -> "Electric Current Statistics""0x2AF2" -> "Energy""0x2AF3" -> "Energy in a Period of Day""0x2AF4" -> "Event Statistics""0x2AF5" -> "Fixed String 16""0x2AF6" -> "Fixed String 24""0x2AF7" -> "Fixed String 36""0x2AF8" -> "Fixed String 8""0x2AF9" -> "Generic Level""0x2AFA" -> "Global Trade Item Number""0x2AFB" -> "Illuminance""0x2AFC" -> "Luminous Efficacy""0x2AFD" -> "Luminous Energy""0x2AFE" -> "Luminous Exposure""0x2AFF" -> "Luminous Flux""0x2B00" -> "Luminous Flux Range""0x2B01" -> "Luminous Intensity""0x2B02" -> "Mass Flow""0x2B03" -> "Perceived Lightness""0x2B04" -> "Percentage 8""0x2B05" -> "Power""0x2B06" -> "Power Specification""0x2B07" -> "Relative Runtime in a Current Range""0x2B08" -> "Relative Runtime in a Generic Level Range""0x2B09" -> "Relative Value in a Voltage Range""0x2B0A" -> "Relative Value in an Illuminance Range""0x2B0B" -> "Relative Value in a Period of Day""0x2B0C" -> "Relative Value in a Temperature Range""0x2B0D" -> "Temperature 8""0x2B0E" -> "Temperature 8 in a Period of Day""0x2B0F" -> "Temperature 8 Statistics""0x2B10" -> "Temperature Range""0x2B11" -> "Temperature Statistics""0x2B12" -> "Time Decihour 8""0x2B13" -> "Time Exponential 8""0x2B14" -> "Time Hour 24""0x2B15" -> "Time Millisecond 24""0x2B16" -> "Time Second 16""0x2B17" -> "Time Second 8""0x2B18" -> "Voltage""0x2B19" -> "Voltage Specification""0x2B1A" -> "Voltage Statistics""0x2B1B" -> "Volume Flow""0x2B1C" -> "Chromaticity Coordinate""0x2B1D" -> "RC Feature""0x2B1E" -> "RC Settings""0x2B1F" -> "Reconnection Configuration Control Point""0x2B20" -> "IDD Status Changed""0x2B21" -> "IDD Status""0x2B22" -> "IDD Annunciation Status""0x2B23" -> "IDD Features""0x2B24" -> "IDD Status Reader Control Point""0x2B25" -> "IDD Command Control Point""0x2B26" -> "IDD Command Data""0x2B27" -> "IDD Record Access Control Point""0x2B28" -> "IDD History Data""0x2B29" -> "Client Supported Features""0x2B2A" -> "Database Hash""0x2B2B" -> "BSS Control Point""0x2B2C" -> "BSS Response""0x2B2D" -> "Emergency ID""0x2B2E" -> "Emergency Text""0x2B2F" -> "ACS Status""0x2B30" -> "ACS Data In""0x2B31" -> "ACS Data Out Notify""0x2B32" -> "ACS Data Out Indicate""0x2B33" -> "ACS Control Point""0x2B34" -> "Enhanced Blood Pressure Measurement""0x2B35" -> "Enhanced Intermediate Cuff Pressure""0x2B36" -> "Blood Pressure Record""0x2B37" -> "Registered User""0x2B38" -> "BR - EDR Handover Data""0x2B39" -> "Bluetooth SIG Data""0x2B3A" -> "Server Supported Features""0x2B3B" -> "Physical Activity Monitor Features""0x2B3C" -> "General Activity Instantaneous Data""0x2B3D" -> "General Activity Summary Data""0x2B3E" -> "CardioRespiratory Activity Instantaneous Data""0x2B3F" -> "CardioRespiratory Activity Summary Data""0x2B40" -> "Step Counter Activity Summary Data""0x2B41" -> "Sleep Activity Instantaneous Data""0x2B42" -> "Sleep Activity Summary Data""0x2B43" -> "Physical Activity Monitor Control Point""0x2B44" -> "Activity Current Session""0x2B45" -> "Physical Activity Session Descriptor""0x2B46" -> "Preferred Units""0x2B47" -> "High Resolution Height""0x2B48" -> "Middle Name""0x2B49" -> "Stride Length""0x2B4A" -> "Handedness""0x2B4B" -> "Device Wearing Position""0x2B4C" -> "Four Zone Heart Rate Limits""0x2B4D" -> "High Intensity Exercise Threshold""0x2B4E" -> "Activity Goal""0x2B4F" -> "Sedentary Interval Notification""0x2B50" -> "Caloric Intake""0x2B51" -> "TMAP Role""0x2B77" -> "Audio Input State""0x2B78" -> "Gain Settings Attribute""0x2B79" -> "Audio Input Type""0x2B7A" -> "Audio Input Status""0x2B7B" -> "Audio Input Control Point""0x2B7C" -> "Audio Input Description""0x2B7D" -> "Volume State""0x2B7E" -> "Volume Control Point""0x2B7F" -> "Volume Flags""0x2B80" -> "Volume Offset State""0x2B81" -> "Audio Location""0x2B82" -> "Volume Offset Control Point""0x2B83" -> "Audio Output Description""0x2B84" -> "Set Identity Resolving Key""0x2B85" -> "Coordinated Set Size""0x2B86" -> "Set Member Lock""0x2B87" -> "Set Member Rank""0x2B88" -> "Encrypted Data Key Material""0x2B89" -> "Apparent Energy 32""0x2B8A" -> "Apparent Power""0x2B8B" -> "Live Health Observations""0x2B8C" -> "CO \\{} text-subscript { 2 } Concentration""0x2B8D" -> "Cosine of the Angle""0x2B8E" -> "Device Time Feature""0x2B8F" -> "Device Time Parameters""0x2B90" -> "Device Time""0x2B91" -> "Device Time Control Point""0x2B92" -> "Time Change Log Data""0x2B93" -> "Media Player Name""0x2B94" -> "Media Player Icon Object ID""0x2B95" -> "Media Player Icon URL""0x2B96" -> "Track Changed""0x2B97" -> "Track Title""0x2B98" -> "Track Duration""0x2B99" -> "Track Position""0x2B9A" -> "Playback Speed""0x2B9B" -> "Seeking Speed""0x2B9C" -> "Current Track Segments Object ID""0x2B9D" -> "Current Track Object ID""0x2B9E" -> "Next Track Object ID""0x2B9F" -> "Parent Group Object ID""0x2BA0" -> "Current Group Object ID""0x2BA1" -> "Playing Order""0x2BA2" -> "Playing Orders Supported""0x2BA3" -> "Media State""0x2BA4" -> "Media Control Point""0x2BA5" -> "Media Control Point Opcodes Supported""0x2BA6" -> "Search Results Object ID""0x2BA7" -> "Search Control Point""0x2BA8" -> "Energy 32""0x2BA9" -> "Media Player Icon Object Type""0x2BAA" -> "Track Segments Object Type""0x2BAB" -> "Track Object Type""0x2BAC" -> "Group Object Type""0x2BAD" -> "Constant Tone Extension Enable""0x2BAE" -> "Advertising Constant Tone Extension Minimum Length""0x2BAF" -> "Advertising Constant Tone Extension Minimum Transmit Count""0x2BB0" -> "Advertising Constant Tone Extension Transmit Duration""0x2BB1" -> "Advertising Constant Tone Extension Interval""0x2BB2" -> "Advertising Constant Tone Extension PHY""0x2BB3" -> "Bearer Provider Name""0x2BB4" -> "Bearer UCI""0x2BB5" -> "Bearer Technology""0x2BB6" -> "Bearer URI Schemes Supported List""0x2BB7" -> "Bearer Signal Strength""0x2BB8" -> "Bearer Signal Strength Reporting Interval""0x2BB9" -> "Bearer List Current Calls""0x2BBA" -> "Content Control ID""0x2BBB" -> "Status Flags""0x2BBC" -> "Incoming Call Target Bearer URI""0x2BBD" -> "Call State""0x2BBE" -> "Call Control Point""0x2BBF" -> "Call Control Point Optional Opcodes""0x2BC0" -> "Termination Reason""0x2BC1" -> "Incoming Call""0x2BC2" -> "Call Friendly Name""0x2BC3" -> "Mute""0x2BC4" -> "Sink ASE""0x2BC5" -> "Source ASE""0x2BC6" -> "ASE Control Point""0x2BC7" -> "Broadcast Audio Scan Control Point""0x2BC8" -> "Broadcast Receive State""0x2BC9" -> "Sink PAC""0x2BCA" -> "Sink Audio Locations""0x2BCB" -> "Source PAC""0x2BCC" -> "Source Audio Locations""0x2BCD" -> "Available Audio Contexts""0x2BCE" -> "Supported Audio Contexts""0x2BCF" -> "Ammonia Concentration""0x2BD0" -> "Carbon Monoxide Concentration""0x2BD1" -> "Methane Concentration""0x2BD2" -> "Nitrogen Dioxide Concentration""0x2BD3" -> "Non -Methane Volatile Organic Compounds Concentration""0x2BD4" -> "Ozone Concentration""0x2BD5" -> "Particulate Matter - PM1 Concentration""0x2BD6" -> "Particulate Matter - PM2.5 Concentration""0x2BD7" -> "Particulate Matter - PM10 Concentration""0x2BD8" -> "Sulfur Dioxide Concentration""0x2BD9" -> "Sulfur Hexafluoride Concentration""0x2BDA" -> "Hearing Aid Features""0x2BDB" -> "Hearing Aid Preset Control Point""0x2BDC" -> "Active Preset Index""0x2BDD" -> "Stored Health Observations""0x2BDE" -> "Fixed String 64""0x2BDF" -> "High Temperature""0x2BE0" -> "High Voltage""0x2BE1" -> "Light Distribution""0x2BE2" -> "Light Output""0x2BE3" -> "Light Source Type""0x2BE4" -> "Noise""0x2BE5" -> "Relative Runtime in a Correlated Color Temperature Range""0x2BE6" -> "Time Second 32""0x2BE7" -> "VOC Concentration""0x2BE8" -> "Voltage Frequency""0x2BE9" -> "Battery Critical Status""0x2BEA" -> "Battery Health Status""0x2BEB" -> "Battery Health Information""0x2BEC" -> "Battery Information""0x2BED" -> "Battery Level Status""0x2BEE" -> "Battery Time Status""0x2BEF" -> "Estimated Service Date""0x2BF0" -> "Battery Energy Status""0x2BF1" -> "Observation Schedule Changed""0x2BF2" -> "Current Elapsed Time""0x2BF3" -> "Health Sensor Features""0x2BF4" -> "GHS Control Point""0x2BF5" -> "LE GATT Security Levels""0x2BF6" -> "ESL Address""0x2BF7" -> "AP Sync Key Material""0x2BF8" -> "ESL Response Key Material""0x2BF9" -> "ESL Current Absolute Time""0x2BFA" -> "ESL Display Information""0x2BFB" -> "ESL Image Information""0x2BFC" -> "ESL Sensor Information""0x2BFD" -> "ESL LED Information""0x2BFE" -> "ESL Control Point""0x2BFF" -> "UDI for Medical Devices"else -> "Unknown Characteristics"}
同修改一下原有的getServiceUUID()
为getShortUUID()
,只改名字而已,之前命名有点不太严谨。
四、特性适配器
首先我们在layout下创建一个item_characteristic.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="1dp"android:foreground="?attr/selectableItemBackground"android:paddingStart="16dp"><TextViewandroid:id="@+id/tv_character_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:text="特性"android:textColor="@color/black"android:textSize="16sp"android:textStyle="bold"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_uuid_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="UUID:"app:layout_constraintStart_toStartOf="@+id/tv_character_name"app:layout_constraintTop_toBottomOf="@+id/tv_character_name" /><TextViewandroid:id="@+id/tv_character_uuid"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="UUID"android:textColor="@color/black"app:layout_constraintBottom_toBottomOf="@+id/tv_uuid_title"app:layout_constraintStart_toEndOf="@+id/tv_uuid_title"app:layout_constraintTop_toTopOf="@+id/tv_uuid_title" /><TextViewandroid:id="@+id/tv_property_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:text="Properties:"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="@+id/tv_character_name"app:layout_constraintTop_toBottomOf="@+id/tv_uuid_title" /><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_property"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="@+id/tv_property_title"app:layout_constraintStart_toEndOf="@+id/tv_property_title"app:layout_constraintTop_toTopOf="@+id/tv_property_title" /></androidx.constraintlayout.widget.ConstraintLayout>
这里显示特性的名称和UUIID,同时加载属性列表,然后写适配器,因为需要操作属性的缘故,这些写一个接口,在adapter包下新建一个OperateCallback接口,代码如下所示:
interface OperateCallback {/*** 属性操作*/fun onPropertyOperate(characteristic: BluetoothGattCharacteristic, operateName: String)
}
通过这个接口可以知道当前操作的是那个特性和属性名称。下面我们写适配器,在adapter包下新建一个CharacteristicAdapter类,代码如下所示:
class CharacteristicAdapter(private val characteristics: List<BluetoothGattCharacteristic>,private val callback: OperateCallback
) : RecyclerView.Adapter<CharacteristicAdapter.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {return ViewHolder(ItemCharacteristicBinding.inflate(LayoutInflater.from(parent.context), parent, false))}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.binding.tvCharacterName.text = BleUtils.getCharacteristicsName(characteristics[position].uuid)holder.binding.tvCharacterUuid.text = BleUtils.getShortUUID(characteristics[position].uuid)//加载特性下的属性holder.binding.rvProperty.apply {layoutManager = LinearLayoutManager(context).apply { orientation = LinearLayoutManager.HORIZONTAL }val properties: List<String> = BleUtils.getProperties(characteristics[position].properties)adapter = PropertyAdapter(properties, object : OnItemClickListener {//点击属性override fun onItemClick(view: View?, position: Int) { callback.onPropertyOperate(characteristics[position], properties[position]) }})}}override fun getItemCount() = characteristics.sizeclass ViewHolder(itemView: ItemCharacteristicBinding) : RecyclerView.ViewHolder(itemView.root) {var binding: ItemCharacteristicBindinginit {binding = itemView}}
}
在这里我们就可以处理特性的名称和UUID显示,同时加载属性适配器,显示出来。
五、加载特性
因为特性是在服务下的,所以我们可以在服务适配器中加载特性适配器。首先我们修改一下item_service.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:orientation="vertical"android:background="@color/white"android:layout_height="wrap_content"><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/item_service"android:layout_width="match_parent"android:foreground="?attr/selectableItemBackground"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:id="@+id/tv_service_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="8dp"android:text="服务"android:textColor="@color/black"android:textSize="16sp"android:textStyle="bold"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_uuid_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="UUID:"app:layout_constraintStart_toStartOf="@+id/tv_service_name"app:layout_constraintTop_toBottomOf="@+id/tv_service_name" /><TextViewandroid:id="@+id/tv_service_uuid"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="UUID"android:textColor="@color/black"app:layout_constraintBottom_toBottomOf="@+id/tv_uuid_title"app:layout_constraintStart_toEndOf="@+id/tv_uuid_title"app:layout_constraintTop_toTopOf="@+id/tv_uuid_title" /><TextViewandroid:id="@+id/tv_service_info"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:text="PRIMARY SERVICE"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="@+id/tv_service_name"app:layout_constraintTop_toBottomOf="@+id/tv_uuid_title" /><ImageViewandroid:id="@+id/iv_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"app:srcCompat="@drawable/ic_right_24" /></androidx.constraintlayout.widget.ConstraintLayout><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_characteristics"android:visibility="gone"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingStart="16dp" />
</LinearLayout>
整体上变化不大,只是加了一个RecyclerView,同时增加了一个ImageView,用于显示当前的服务是否展开,可以通过当前的服务item的方式控制是否显示特性列表,这里用到两个图标,在drawable下创建ic_right_24.xml
,代码如下所示:
<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:tint="#000000"android:viewportWidth="24"android:viewportHeight="24"><pathandroid:fillColor="@android:color/white"android:pathData="M10,17l5,-5 -5,-5v10z" />
</vector>
还有一个ic_down_24.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:tint="#000000"android:viewportWidth="24"android:viewportHeight="24"><pathandroid:fillColor="@android:color/white"android:pathData="M7,10l5,5 5,-5z" />
</vector>
下面修改一下ServiceAdapter,代码如下所示:
class ServiceAdapter(private val services: List<BluetoothGattService>,private val callback: OperateCallback
) : RecyclerView.Adapter<ServiceAdapter.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val viewHolder = ViewHolder(ItemServiceBinding.inflate(LayoutInflater.from(parent.context), parent, false))viewHolder.binding.itemService.setOnClickListener {//显示特性列表viewHolder.binding.rvCharacteristics.visibility = if (viewHolder.binding.rvCharacteristics.visibility == View.VISIBLE) View.GONE else View.VISIBLE//更换图标viewHolder.binding.ivState.setImageDrawable(if (viewHolder.binding.rvCharacteristics.visibility == View.VISIBLE) ContextCompat.getDrawable(parent.context, R.drawable.ic_down_24)else ContextCompat.getDrawable(parent.context, R.drawable.ic_right_24))}return viewHolder}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.binding.tvServiceName.text = BleUtils.getServiceName(services[position].uuid)holder.binding.tvServiceUuid.text = BleUtils.getShortUUID(services[position].uuid)//加载服务下的特性holder.binding.rvCharacteristics.apply {layoutManager = LinearLayoutManager(context)adapter = CharacteristicAdapter(services[position].characteristics, callback)}}override fun getItemCount() = services.sizeclass ViewHolder(itemView: ItemServiceBinding) : RecyclerView.ViewHolder(itemView.root) {var binding: ItemServiceBindinginit {binding = itemView}}
}
和之前的区别就在于构造的时候增加了一个回调,并且在onCreateViewHolder()
函数中就处理了服务Item的点击事件,而不是像之前一样回调到Activity中,在服务Item的点击事件中判断是否显示特性列表同时修改图标资源。最后再将接口回调到Activity中。
六、显示特性和属性
现在要做的就是修改MainActivity中的代码,首先修改activity_main.xml中的代码,主要是修改之前的rv_service中的属性值,修改后如下所示:
<androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_service"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_device_info" />
主要就是让RecyclerView占满剩下的空间,避免内容过多导致无法滑动的情况,最后我们修改一下MainActivity中代码,如下所示:
class MainActivity : BaseActivity(), BleCallback, OperateCallback {...override fun onServicesDiscovered(services: List<BluetoothGattService>) {runOnUiThread {mServiceList.clear()mServiceList.addAll(services)mServiceAdapter ?: run {mServiceAdapter = ServiceAdapter(mServiceList, this@MainActivity)binding.rvService.apply {(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = falselayoutManager = LinearLayoutManager(this@MainActivity)adapter = mServiceAdapter//增加分隔线addItemDecoration(DividerItemDecoration(this@MainActivity, DividerItemDecoration.VERTICAL))}mServiceAdapter}mServiceAdapter!!.notifyDataSetChanged()}}/*** 属性操作*/override fun onPropertyOperate(characteristic: BluetoothGattCharacteristic, operateName: String) {showMsg(operateName)}
}
修改的地方有三个,第一个就是MainActivity
实现OnItemClickListener
改成OperateCallback
,第二个是onServicesDiscovered()
函数中,构建ServiceAdapter适配器中实现接口,然后添加分隔线,最后一个就是去掉之前onItemClick()
函数改成onPropertyOperate()
函数,运行一下看看效果。
七、源码
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:GoodBle