Ble蓝牙App(三)特性使用
- 前言
- 正文
- 一、获取属性列表
- 二、属性提供者
- 三、获取特性名称
- 四、特性提供者
- 五、加载特性
- 六、源码
前言
在上一篇中我们完成了连接和发现服务两个动作,那么再发现服务之后要做什么呢?发现服务只是让你知道设备有什么服务,可以做什么事情。
正文
本篇要做的是显示服务下的特性,首先我们了解一下特性的基本知识。在蓝牙低功耗(BLE)中,特性(Characteristic)
是蓝牙设备提供的一种数据单元,用于描述设备的某个属性或功能。特性包含了一系列的属性和值,可以用于读取、写入和通知数据。
BLE特性相关的关键概念和说明:
UUID(Universally Unique Identifier)
:每个特性都会有一个唯一的UUID,用于标识该特性。值(Value)
:特性包含一个值,可以是字节数组、字符串或其他数据类型。该值代表特性的当前状态或数据内容。属性(Properties)
:特性具有一组属性,包括读、写、通知等。属性决定了可以对特性进行哪些操作。读(Read)
:允许外部设备从特性中读取当前的值。写(Write)
:允许外部设备向特性写入一个新的值。通知(Notify)
:当特性的值发生变化时,可以通过通知方式将新的值发送给订阅该特性的外部设备。描述符(Descriptor)
:特性可以附带一个或多个描述符,用于提供关于特性的额外信息或配置。
使用BLE特性,可以实现各种功能和数据交互,例如传感器数据的读取、设备状态的监控、远程控制等。特性的读写和通知操作可以通过与蓝牙设备的交互来实现。需要注意的是,BLE特性的操作和功能是由设备的厂商定义的,并在设备的GATT(Generic Attribute Profile)配置文件中进行描述。
首先理清一下思路,我们现在知道服务下面有特性,特性下面有一些属性值,其中属性(Properties)
尤为重要,因为它决定了你的特性可以进行那些操作。用一个图来说明服务,特性,属性之间的关系。
一、获取属性列表
下面我们先获取最下面的属性,这是一个列表,属性值的处理有一些不同,首先我们在BleUtils中增加一个函数,代码如下所示:
/*** 获取属性*/public static List<String> getProperties(int property) {List<String> properties = new ArrayList<>();for (int i = 0; i < 8; i++) {switch (property & (1 << i)) {case 0x01:properties.add("Broadcast");break;case 0x02:properties.add("Read");break;case 0x04:properties.add("Write No Response");break;case 0x08:properties.add("Write");break;case 0x10:properties.add("Notify");break;case 0x20:properties.add("Indicate");break;case 0x40:properties.add("Authenticated Signed Writes");break;case 0x80:properties.add("Extended Properties");break;}}return properties;}
这里是通过位运算进行计算属性的值,首先是循环遍历,先左移再按位与,得到最终的值,根据值得到属性描述,这些描述就是具体的功能操作。会返回一个属性列表,有了列表我们就可以写一个适配器了。
二、属性提供者
首先我们在layout下创建一个item_property.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<Textxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:id="$+id:tx_property"ohos:height="match_content"ohos:width="match_content"ohos:end_margin="8vp"ohos:text="property"ohos:text_color="$color:blue"ohos:text_size="14fp"/>
因为是String类型,所以我们就直接用一个Text显示即可,下面我们写提供者,在provider
包下新建一个PropertyProvider
类,代码如下所示:
public class PropertyProvider extends BaseItemProvider {private final List<String> propertyList;private final AbilitySlice slice;public PropertyProvider(List<String> list, AbilitySlice slice) {this.propertyList = list;this.slice = slice;}@Overridepublic int getCount() {return propertyList == null ? 0 : propertyList.size();}@Overridepublic Object getItem(int position) {if (propertyList != null && position >= 0 && position < propertyList.size()) {return propertyList.get(position);}return null;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic Component getComponent(int position, Component component, ComponentContainer componentContainer) {final Component cpt;ServiceHolder holder;if (component == null) {cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_property, null, false);holder = new ServiceHolder(cpt);//将获取到的子组件信息绑定到列表项的实例中cpt.setTag(holder);} else {cpt = component;// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。holder = (ServiceHolder) cpt.getTag();}holder.txProperty.setText(propertyList.get(position));return cpt;}/*** 用于保存列表项的子组件信息*/public static class ServiceHolder {Text txProperty;public ServiceHolder(Component component) {txProperty = (Text) component.findComponentById(ResourceTable.Id_tx_property);}}
}
这里进行了属性的点击监听,我们可以回调到特性适配器中去处理,下面我们要处理的就是特性了。
三、获取特性名称
首先是特性名称,同样是根据UUID,同样是那个PDF文档,在BleUtils中增加一个getCharacteristicsName()
函数,代码有点多,如下所示:
/*** 获取特性名称* @param uuid UUID*/public static String getCharacteristicsName(UUID uuid) {String targetUuid = getShortUUID(uuid);switch (targetUuid) {case "0x2A00":return "Device Name";case "0x2A01":return "Appearance";case "0x2A02":return "Peripheral Privacy Flag";case "0x2A03":return "Reconnection Address";case "0x2A04":return "Peripheral Preferred Connection Parameters";case "0x2A05":return "Service Changed";case "0x2A06":return "Alert Level";case "0x2A07":return "Tx Power Level";case "0x2A08":return "Date Time";case "0x2A09":return "Day of Week";case "0x2A0A":return "Day Date Time";case "0x2A0C":return "Exact Time 256";case "0x2A0D":return "DST Offset";case "0x2A0E":return "Time Zone";case "0x2A0F":return "Local Time Information";case "0x2A11":return "Time with DST";case "0x2A12":return "Time Accuracy";case "0x2A13":return "Time Source";case "0x2A14":return "Reference Time Information";case "0x2A16":return "Time Update Control Point";case "0x2A17":return "Time Update State";case "0x2A18":return "Glucose Measurement";case "0x2A19":return "Battery Level";case "0x2A1C":return "Temperature Measurement";case "0x2A1D":return "Temperature Type";case "0x2A1E":return "Intermediate Temperature";case "0x2A21":return "Measurement Interval";case "0x2A22":return "Boot Keyboard Input Report";case "0x2A23":return "System ID";case "0x2A24":return "Model Number String";case "0x2A25":return "Serial Number String";case "0x2A26":return "Firmware Revision String";case "0x2A27":return "Hardware Revision String";case "0x2A28":return "Software Revision String";case "0x2A29":return "Manufacturer Name String";case "0x2A2A":return "IEEE 11073-20601 Regulatory Certification Data List";case "0x2A2B":return "Current Time";case "0x2A2C":return "Magnetic Declination";case "0x2A31":return "Scan Refresh";case "0x2A32":return "Boot Keyboard Output Report";case "0x2A33":return "Boot Mouse Input Report";case "0x2A34":return "Glucose Measurement Context";case "0x2A35":return "Blood Pressure Measurement";case "0x2A36":return "Intermediate Cuff Pressure";case "0x2A37":return "Heart Rate Measurement";case "0x2A38":return "Body Sensor Location";case "0x2A39":return "Heart Rate Control Point";case "0x2A3F":return "Alert Status";case "0x2A40":return "Ringer Control Point";case "0x2A41":return "Ringer Setting";case "0x2A42":return "Alert Category ID Bit Mask";case "0x2A43":return "Alert Category ID";case "0x2A44":return "Alert Notification Control Point";case "0x2A45":return "Unread Alert Status";case "0x2A46":return "New Alert";case "0x2A47":return "Supported New Alert Category";case "0x2A48":return "Supported Unread Alert Category";case "0x2A49":return "Blood Pressure Feature";case "0x2A4A":return "HID Information";case "0x2A4B":return "Report Map";case "0x2A4C":return "HID Control Point";case "0x2A4D":return "Report";case "0x2A4E":return "Protocol Mode";case "0x2A4F":return "Scan Interval Window";case "0x2A50":return "PnP ID";case "0x2A51":return "Glucose Feature";case "0x2A52":return "Record Access Control Point";case "0x2A53":return "RSC Measurement";case "0x2A54":return "RSC Feature";case "0x2A55":return "SC Control Point";case "0x2A5A":return "Aggregate";case "0x2A5B":return "CSC Measurement";case "0x2A5C":return "CSC Feature";case "0x2A5D":return "Sensor Location";case "0x2A5E":return "PLX Spot-Check Measurement";case "0x2A5F":return "PLX Continuous Measurement";case "0x2A60":return "PLX Features";case "0x2A63":return "Cycling Power Measurement";case "0x2A64":return "Cycling Power Vector";case "0x2A65":return "Cycling Power Feature";case "0x2A66":return "Cycling Power Control Point";case "0x2A67":return "Location and Speed";case "0x2A68":return "Navigation";case "0x2A69":return "Position Quality";case "0x2A6A":return "LN Feature";case "0x2A6B":return "LN Control Point";case "0x2A6C":return "Elevation";case "0x2A6D":return "Pressure";case "0x2A6E":return "Temperature";case "0x2A6F":return "Humidity";case "0x2A70":return "True Wind Speed";case "0x2A71":return "True Wind Direction";case "0x2A72":return "Apparent Wind Speed";case "0x2A73":return "Apparent Wind Direction";case "0x2A74":return "Gust Factor";case "0x2A75":return "Pollen Concentration";case "0x2A76":return "UV Index";case "0x2A77":return "Irradiance";case "0x2A78":return "Rainfall";case "0x2A79":return "Wind Chill";case "0x2A7A":return "Heat Index";case "0x2A7B":return "Dew Point";case "0x2A7D":return "Descriptor Value Changed";case "0x2A7E":return "Aerobic Heart Rate Lower Limit";case "0x2A7F":return "Aerobic Threshold";case "0x2A80":return "Age";case "0x2A81":return "Anaerobic Heart Rate Lower Limit";case "0x2A82":return "Anaerobic Heart Rate Upper Limit";case "0x2A83":return "Anaerobic Threshold";case "0x2A84":return "Aerobic Heart Rate Upper Limit";case "0x2A85":return "Date of Birth";case "0x2A86":return "Date of Threshold Assessment";case "0x2A87":return "Email Address";case "0x2A88":return "Fat Burn Heart Rate Lower Limit";case "0x2A89":return "Fat Burn Heart Rate Upper Limit";case "0x2A8A":return "First Name";case "0x2A8B":return "Five Zone Heart Rate Limits";case "0x2A8C":return "Gender";case "0x2A8D":return "Heart Rate Max";case "0x2A8E":return "Height";case "0x2A8F":return "Hip Circumference";case "0x2A90":return "Last Name";case "0x2A91":return "Maximum Recommended Heart Rate";case "0x2A92":return "Resting Heart Rate";case "0x2A93":return "Sport Type for Aerobic and Anaerobic Thresholds";case "0x2A94":return "Three Zone Heart Rate Limits";case "0x2A95":return "Two Zone Heart Rate Limits";case "0x2A96":return "VO2 Max";case "0x2A97":return "Waist Circumference";case "0x2A98":return "Weight";case "0x2A99":return "Database Change Increment";case "0x2A9A":return "User Index";case "0x2A9B":return "Body Composition Feature";case "0x2A9C":return "Body Composition Measurement";case "0x2A9D":return "Weight Measurement";case "0x2A9E":return "Weight Scale Feature";case "0x2A9F":return "User Control Point";case "0x2AA0":return "Magnetic Flux Density - 2D";case "0x2AA1":return "Magnetic Flux Density - 3D";case "0x2AA2":return "Language";case "0x2AA3":return "Barometric Pressure Trend";case "0x2AA4":return "Bond Management Control Point";case "0x2AA5":return "Bond Management Feature";case "0x2AA6":return "Central Address Resolution";case "0x2AA7":return "CGM Measurement";case "0x2AA8":return "CGM Feature";case "0x2AA9":return "CGM Status";case "0x2AAA":return "CGM Session Start Time";case "0x2AAB":return "CGM Session Run Time";case "0x2AAC":return "CGM Specific Ops Control Point";case "0x2AAD":return "Indoor Positioning Configuration";case "0x2AAE":return "Latitude";case "0x2AAF":return "Longitude";case "0x2AB0":return "Local North Coordinate";case "0x2AB1":return "Local East Coordinate";case "0x2AB2":return "Floor Number";case "0x2AB3":return "Altitude";case "0x2AB4":return "Uncertainty";case "0x2AB5":return "Location Name";case "0x2AB6":return "URI";case "0x2AB7":return "HTTP Headers";case "0x2AB8":return "HTTP Status Code";case "0x2AB9":return "HTTP Entity Body";case "0x2ABA":return "HTTP Control Point";case "0x2ABB":return "HTTPS Security";case "0x2ABC":return "TDS Control Point";case "0x2ABD":return "OTS Feature";case "0x2ABE":return "Object Name";case "0x2ABF":return "Object Type";case "0x2AC0":return "Object Size";case "0x2AC1":return "Object First -Created";case "0x2AC2":return "Object Last - Modified";case "0x2AC3":return "Object ID";case "0x2AC4":return "Object Properties";case "0x2AC5":return "Object Action Control Point";case "0x2AC6":return "Object List Control Point";case "0x2AC7":return "Object List Filter";case "0x2AC8":return "Object Changed";case "0x2AC9":return "Resolvable Private Address Only";case "0x2ACC":return "Fitness Machine Feature";case "0x2ACD":return "Treadmill Data";case "0x2ACE":return "Cross Trainer Data";case "0x2ACF":return "Step Climber Data";case "0x2AD0":return "Stair Climber Data";case "0x2AD1":return "Rower Data";case "0x2AD2":return "Indoor Bike Data";case "0x2AD3":return "Training Status";case "0x2AD4":return "Supported Speed Range";case "0x2AD5":return "Supported Inclination Range";case "0x2AD6":return "Supported Resistance Level Range";case "0x2AD7":return "Supported Heart Rate Range";case "0x2AD8":return "Supported Power Range";case "0x2AD9":return "Fitness Machine Control Point";case "0x2ADA":return "Fitness Machine Status";case "0x2ADB":return "Mesh Provisioning Data In";case "0x2ADC":return "Mesh Provisioning Data Out";case "0x2ADD":return "Mesh Proxy Data In";case "0x2ADE":return "Mesh Proxy Data Out";case "0x2AE0":return "Average Current";case "0x2AE1":return "Average Voltage";case "0x2AE2":return "Boolean";case "0x2AE3":return "Chromatic Distance from Planckian";case "0x2AE4":return "Chromaticity Coordinates";case "0x2AE5":return "Chromaticity in CCT and Duv Values";case "0x2AE6":return "Chromaticity Tolerance";case "0x2AE7":return "CIE 13.3 - 1995 Color Rendering Index";case "0x2AE8":return "Coefficient";case "0x2AE9":return "Correlated Color Temperature";case "0x2AEA":return "Count 16";case "0x2AEB":return "Count 24";case "0x2AEC":return "Country Code";case "0x2AED":return "Date UTC";case "0x2AEE":return "Electric Current";case "0x2AEF":return "Electric Current Range";case "0x2AF0":return "Electric Current Specification";case "0x2AF1":return "Electric Current Statistics";case "0x2AF2":return "Energy";case "0x2AF3":return "Energy in a Period of Day";case "0x2AF4":return "Event Statistics";case "0x2AF5":return "Fixed String 16";case "0x2AF6":return "Fixed String 24";case "0x2AF7":return "Fixed String 36";case "0x2AF8":return "Fixed String 8";case "0x2AF9":return "Generic Level";case "0x2AFA":return "Global Trade Item Number";case "0x2AFB":return "Illuminance";case "0x2AFC":return "Luminous Efficacy";case "0x2AFD":return "Luminous Energy";case "0x2AFE":return "Luminous Exposure";case "0x2AFF":return "Luminous Flux";case "0x2B00":return "Luminous Flux Range";case "0x2B01":return "Luminous Intensity";case "0x2B02":return "Mass Flow";case "0x2B03":return "Perceived Lightness";case "0x2B04":return "Percentage 8";case "0x2B05":return "Power";case "0x2B06":return "Power Specification";case "0x2B07":return "Relative Runtime in a Current Range";case "0x2B08":return "Relative Runtime in a Generic Level Range";case "0x2B09":return "Relative Value in a Voltage Range";case "0x2B0A":return "Relative Value in an Illuminance Range";case "0x2B0B":return "Relative Value in a Period of Day";case "0x2B0C":return "Relative Value in a Temperature Range";case "0x2B0D":return "Temperature 8";case "0x2B0E":return "Temperature 8 in a Period of Day";case "0x2B0F":return "Temperature 8 Statistics";case "0x2B10":return "Temperature Range";case "0x2B11":return "Temperature Statistics";case "0x2B12":return "Time Decihour 8";case "0x2B13":return "Time Exponential 8";case "0x2B14":return "Time Hour 24";case "0x2B15":return "Time Millisecond 24";case "0x2B16":return "Time Second 16";case "0x2B17":return "Time Second 8";case "0x2B18":return "Voltage";case "0x2B19":return "Voltage Specification";case "0x2B1A":return "Voltage Statistics";case "0x2B1B":return "Volume Flow";case "0x2B1C":return "Chromaticity Coordinate";case "0x2B1D":return "RC Feature";case "0x2B1E":return "RC Settings";case "0x2B1F":return "Reconnection Configuration Control Point";case "0x2B20":return "IDD Status Changed";case "0x2B21":return "IDD Status";case "0x2B22":return "IDD Annunciation Status";case "0x2B23":return "IDD Features";case "0x2B24":return "IDD Status Reader Control Point";case "0x2B25":return "IDD Command Control Point";case "0x2B26":return "IDD Command Data";case "0x2B27":return "IDD Record Access Control Point";case "0x2B28":return "IDD History Data";case "0x2B29":return "Client Supported Features";case "0x2B2A":return "Database Hash";case "0x2B2B":return "BSS Control Point";case "0x2B2C":return "BSS Response";case "0x2B2D":return "Emergency ID";case "0x2B2E":return "Emergency Text";case "0x2B2F":return "ACS Status";case "0x2B30":return "ACS Data In";case "0x2B31":return "ACS Data Out Notify";case "0x2B32":return "ACS Data Out Indicate";case "0x2B33":return "ACS Control Point";case "0x2B34":return "Enhanced Blood Pressure Measurement";case "0x2B35":return "Enhanced Intermediate Cuff Pressure";case "0x2B36":return "Blood Pressure Record";case "0x2B37":return "Registered User";case "0x2B38":return "BR - EDR Handover Data";case "0x2B39":return "Bluetooth SIG Data";case "0x2B3A":return "Server Supported Features";case "0x2B3B":return "Physical Activity Monitor Features";case "0x2B3C":return "General Activity Instantaneous Data";case "0x2B3D":return "General Activity Summary Data";case "0x2B3E":return "CardioRespiratory Activity Instantaneous Data";case "0x2B3F":return "CardioRespiratory Activity Summary Data";case "0x2B40":return "Step Counter Activity Summary Data";case "0x2B41":return "Sleep Activity Instantaneous Data";case "0x2B42":return "Sleep Activity Summary Data";case "0x2B43":return "Physical Activity Monitor Control Point";case "0x2B44":return "Activity Current Session";case "0x2B45":return "Physical Activity Session Descriptor";case "0x2B46":return "Preferred Units";case "0x2B47":return "High Resolution Height";case "0x2B48":return "Middle Name";case "0x2B49":return "Stride Length";case "0x2B4A":return "Handedness";case "0x2B4B":return "Device Wearing Position";case "0x2B4C":return "Four Zone Heart Rate Limits";case "0x2B4D":return "High Intensity Exercise Threshold";case "0x2B4E":return "Activity Goal";case "0x2B4F":return "Sedentary Interval Notification";case "0x2B50":return "Caloric Intake";case "0x2B51":return "TMAP Role";case "0x2B77":return "Audio Input State";case "0x2B78":return "Gain Settings Attribute";case "0x2B79":return "Audio Input Type";case "0x2B7A":return "Audio Input Status";case "0x2B7B":return "Audio Input Control Point";case "0x2B7C":return "Audio Input Description";case "0x2B7D":return "Volume State";case "0x2B7E":return "Volume Control Point";case "0x2B7F":return "Volume Flags";case "0x2B80":return "Volume Offset State";case "0x2B81":return "Audio Location";case "0x2B82":return "Volume Offset Control Point";case "0x2B83":return "Audio Output Description";case "0x2B84":return "Set Identity Resolving Key";case "0x2B85":return "Coordinated Set Size";case "0x2B86":return "Set Member Lock";case "0x2B87":return "Set Member Rank";case "0x2B88":return "Encrypted Data Key Material";case "0x2B89":return "Apparent Energy 32";case "0x2B8A":return "Apparent Power";case "0x2B8B":return "Live Health Observations";case "0x2B8C":return "CO \\{} text-subscript { 2 } Concentration";case "0x2B8D":return "Cosine of the Angle";case "0x2B8E":return "Device Time Feature";case "0x2B8F":return "Device Time Parameters";case "0x2B90":return "Device Time";case "0x2B91":return "Device Time Control Point";case "0x2B92":return "Time Change Log Data";case "0x2B93":return "Media Player Name";case "0x2B94":return "Media Player Icon Object ID";case "0x2B95":return "Media Player Icon URL";case "0x2B96":return "Track Changed";case "0x2B97":return "Track Title";case "0x2B98":return "Track Duration";case "0x2B99":return "Track Position";case "0x2B9A":return "Playback Speed";case "0x2B9B":return "Seeking Speed";case "0x2B9C":return "Current Track Segments Object ID";case "0x2B9D":return "Current Track Object ID";case "0x2B9E":return "Next Track Object ID";case "0x2B9F":return "Parent Group Object ID";case "0x2BA0":return "Current Group Object ID";case "0x2BA1":return "Playing Order";case "0x2BA2":return "Playing Orders Supported";case "0x2BA3":return "Media State";case "0x2BA4":return "Media Control Point";case "0x2BA5":return "Media Control Point Opcodes Supported";case "0x2BA6":return "Search Results Object ID";case "0x2BA7":return "Search Control Point";case "0x2BA8":return "Energy 32";case "0x2BA9":return "Media Player Icon Object Type";case "0x2BAA":return "Track Segments Object Type";case "0x2BAB":return "Track Object Type";case "0x2BAC":return "Group Object Type";case "0x2BAD":return "Constant Tone Extension Enable";case "0x2BAE":return "Advertising Constant Tone Extension Minimum Length";case "0x2BAF":return "Advertising Constant Tone Extension Minimum Transmit Count";case "0x2BB0":return "Advertising Constant Tone Extension Transmit Duration";case "0x2BB1":return "Advertising Constant Tone Extension Interval";case "0x2BB2":return "Advertising Constant Tone Extension PHY";case "0x2BB3":return "Bearer Provider Name";case "0x2BB4":return "Bearer UCI";case "0x2BB5":return "Bearer Technology";case "0x2BB6":return "Bearer URI Schemes Supported List";case "0x2BB7":return "Bearer Signal Strength";case "0x2BB8":return "Bearer Signal Strength Reporting Interval";case "0x2BB9":return "Bearer List Current Calls";case "0x2BBA":return "Content Control ID";case "0x2BBB":return "Status Flags";case "0x2BBC":return "Incoming Call Target Bearer URI";case "0x2BBD":return "Call State";case "0x2BBE":return "Call Control Point";case "0x2BBF":return "Call Control Point Optional Opcodes";case "0x2BC0":return "Termination Reason";case "0x2BC1":return "Incoming Call";case "0x2BC2":return "Call Friendly Name";case "0x2BC3":return "Mute";case "0x2BC4":return "Sink ASE";case "0x2BC5":return "Source ASE";case "0x2BC6":return "ASE Control Point";case "0x2BC7":return "Broadcast Audio Scan Control Point";case "0x2BC8":return "Broadcast Receive State";case "0x2BC9":return "Sink PAC";case "0x2BCA":return "Sink Audio Locations";case "0x2BCB":return "Source PAC";case "0x2BCC":return "Source Audio Locations";case "0x2BCD":return "Available Audio Contexts";case "0x2BCE":return "Supported Audio Contexts";case "0x2BCF":return "Ammonia Concentration";case "0x2BD0":return "Carbon Monoxide Concentration";case "0x2BD1":return "Methane Concentration";case "0x2BD2":return "Nitrogen Dioxide Concentration";case "0x2BD3":return "Non -Methane Volatile Organic Compounds Concentration";case "0x2BD4":return "Ozone Concentration";case "0x2BD5":return "Particulate Matter - PM1 Concentration";case "0x2BD6":return "Particulate Matter - PM2.5 Concentration";case "0x2BD7":return "Particulate Matter - PM10 Concentration";case "0x2BD8":return "Sulfur Dioxide Concentration";case "0x2BD9":return "Sulfur Hexafluoride Concentration";case "0x2BDA":return "Hearing Aid Features";case "0x2BDB":return "Hearing Aid Preset Control Point";case "0x2BDC":return "Active Preset Index";case "0x2BDD":return "Stored Health Observations";case "0x2BDE":return "Fixed String 64";case "0x2BDF":return "High Temperature";case "0x2BE0":return "High Voltage";case "0x2BE1":return "Light Distribution";case "0x2BE2":return "Light Output";case "0x2BE3":return "Light Source Type";case "0x2BE4":return "Noise";case "0x2BE5":return "Relative Runtime in a Correlated Color Temperature Range";case "0x2BE6":return "Time Second 32";case "0x2BE7":return "VOC Concentration";case "0x2BE8":return "Voltage Frequency";case "0x2BE9":return "Battery Critical Status";case "0x2BEA":return "Battery Health Status";case "0x2BEB":return "Battery Health Information";case "0x2BEC":return "Battery Information";case "0x2BED":return "Battery Level Status";case "0x2BEE":return "Battery Time Status";case "0x2BEF":return "Estimated Service Date";case "0x2BF0":return "Battery Energy Status";case "0x2BF1":return "Observation Schedule Changed";case "0x2BF2":return "Current Elapsed Time";case "0x2BF3":return "Health Sensor Features";case "0x2BF4":return "GHS Control Point";case "0x2BF5":return "LE GATT Security Levels";case "0x2BF6":return "ESL Address";case "0x2BF7":return "AP Sync Key Material";case "0x2BF8":return "ESL Response Key Material";case "0x2BF9":return "ESL Current Absolute Time";case "0x2BFA":return "ESL Display Information";case "0x2BFB":return "ESL Image Information";case "0x2BFC":return "ESL Sensor Information";case "0x2BFD":return "ESL LED Information";case "0x2BFE":return "ESL Control Point";case "0x2BFF":return "UDI for Medical Devices";default:return "Unknown Characteristics";}}
同修改一下原有的getServiceUUID()
为getShortUUID()
,只改名字而已,之前命名有点不太严谨,记得改一下使用的地方,如下图所示,在ServiceProvider
中
四、特性提供者
首先我们在layout下创建一个item_characteristic.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<DependentLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_content"ohos:width="match_parent"ohos:background_element="#FFFFFF"ohos:bottom_margin="2vp"ohos:bottom_padding="8vp"ohos:end_padding="16vp"ohos:start_padding="16vp"ohos:top_padding="8vp"><Textohos:id="$+id:tx_character_name"ohos:height="match_content"ohos:width="match_content"ohos:background_element="$color:black"ohos:text="服务"ohos:text_size="16fp"/><Textohos:id="$+id:tx_uuid_title"ohos:height="match_content"ohos:width="match_content"ohos:below="$id:tx_character_name"ohos:text="UUID:"ohos:text_color="$color:gray"ohos:text_size="16fp"ohos:top_margin="2vp"/><Textohos:id="$+id:tx_uuid"ohos:height="match_content"ohos:width="match_content"ohos:background_element="$color:black"ohos:below="$id:tx_character_name"ohos:end_of="$id:tx_uuid_title"ohos:text="UUID"ohos:text_size="16fp"ohos:top_margin="2vp"/><Textohos:id="$+id:tx_property_title"ohos:height="match_content"ohos:width="match_content"ohos:below="$id:tx_uuid_title"ohos:text="Properties:"ohos:text_color="$color:gray"ohos:text_size="16fp"ohos:top_margin="2vp"/><ListContainerohos:id="$+id:lc_property"ohos:height="match_content"ohos:width="match_parent"ohos:orientation="horizontal"ohos:align_bottom="$id:tx_property_title"ohos:align_top="$id:tx_property_title"ohos:end_of="$id:tx_property_title"/></DependentLayout>
这个布局中唯一说明的一点就是ListContainer
的orientation="horizontal"
属性,这表示列表是横向,默认是纵向的。这里显示特性的名称和UUIID,同时加载属性列表,然后写适配器,因为需要操作属性的缘故,这些写一个接口,在provider
包下新建一个OperateCallback
接口,代码如下所示:
public interface OperateCallback {/*** 属性操作*/void onPropertyOperate(GattCharacteristic characteristic, String operateName);
}
通过这个接口可以知道当前操作的是那个特性和属性名称。下面我们写适配器,在provider
包下新建一个CharacteristicProvider
类,代码如下所示:
public class CharacteristicProvider extends BaseItemProvider {private final List<GattCharacteristic> characteristicList;private final AbilitySlice slice;private final OperateCallback operateCallback;public CharacteristicProvider(List<GattCharacteristic> list, AbilitySlice slice, OperateCallback operateCallback) {this.characteristicList = list;this.slice = slice;this.operateCallback = operateCallback;}@Overridepublic int getCount() {return characteristicList == null ? 0 : characteristicList.size();}@Overridepublic Object getItem(int position) {if (characteristicList != null && position >= 0 && position < characteristicList.size()) {return characteristicList.get(position);}return null;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic Component getComponent(int position, Component component, ComponentContainer componentContainer) {final Component cpt;ServiceHolder holder;GattCharacteristic characteristic = characteristicList.get(position);if (component == null) {cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_characteristic, null, false);holder = new ServiceHolder(cpt);//将获取到的子组件信息绑定到列表项的实例中cpt.setTag(holder);} else {cpt = component;// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。holder = (ServiceHolder) cpt.getTag();}holder.txCharacterName.setText(BleUtils.getCharacteristicsName(characteristic.getUuid()));holder.txUuid.setText(BleUtils.getShortUUID(characteristic.getUuid()));List<String> properties = BleUtils.getProperties(characteristic.getProperties());//加载属性holder.lcProperty.setItemProvider(new PropertyProvider(properties, slice));//属性列表点击holder.lcProperty.setItemClickedListener((listContainer, component1, propertyPosition, l) -> {if (operateCallback != null) {//属性操作回调operateCallback.onPropertyOperate(characteristic, properties.get(propertyPosition));}});return cpt;}/*** 用于保存列表项的子组件信息*/public static class ServiceHolder {Text txCharacterName;Text txUuid;ListContainer lcProperty;public ServiceHolder(Component component) {txCharacterName = (Text) component.findComponentById(ResourceTable.Id_tx_character_name);txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);lcProperty = (ListContainer) component.findComponentById(ResourceTable.Id_lc_property);}}
}
在这里我们就可以处理特性的名称和UUID显示,同时加载属性提供者,显示出来。
五、加载特性
因为特性是在服务下的,所以我们可以在服务适配器中加载特性适配器。首先我们修改一下item_service.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_content"ohos:width="match_parent"ohos:background_element="#FFFFFF"ohos:bottom_margin="2vp"ohos:orientation="vertical"><DependentLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:id="$+id:item_service"ohos:height="match_content"ohos:width="match_parent"ohos:background_element="#FFFFFF"ohos:bottom_padding="8vp"ohos:end_padding="16vp"ohos:start_padding="16vp"ohos:top_padding="8vp"><Textohos:id="$+id:tx_service_name"ohos:height="match_content"ohos:width="match_content"ohos:background_element="$color:black"ohos:text="服务"ohos:text_size="16fp"/><Textohos:id="$+id:tx_uuid_title"ohos:height="match_content"ohos:width="match_content"ohos:below="$id:tx_service_name"ohos:text="UUID:"ohos:text_color="$color:gray"ohos:text_size="16fp"ohos:top_margin="2vp"/><Textohos:id="$+id:tx_uuid"ohos:height="match_content"ohos:width="match_content"ohos:background_element="$color:black"ohos:below="$id:tx_service_name"ohos:end_of="$id:tx_uuid_title"ohos:text="UUID"ohos:text_size="16fp"ohos:top_margin="2vp"/><Textohos:id="$+id:tx_service_info"ohos:height="match_content"ohos:width="match_content"ohos:below="$id:tx_uuid_title"ohos:text="PRIMARY SERVICE"ohos:text_color="$color:gray"ohos:text_size="16fp"ohos:top_margin="2vp"/><Imageohos:id="$+id:iv_state"ohos:height="24vp"ohos:width="24vp"ohos:align_parent_end="true"ohos:end_margin="16vp"ohos:background_element="$graphic:ic_right_24"ohos:vertical_center="true"/></DependentLayout><ListContainerohos:id="$+id:lc_characteristics"ohos:height="match_content"ohos:width="match_parent"ohos:start_padding="16vp"ohos:visibility="hide"/>
</DirectionalLayout>
整体上变化不大,只是加了一个ListContainer
,同时增加了一个Image
,用于显示当前的服务下的特性列表是否显示,可以通过当前的服务item的方式控制是否显示特性列表,这里用到两个图标,在graphic
下创建ic_right_24.xml
,代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<vectorxmlns:ohos="http://schemas.android.com/apk/res/android"ohos:height="24vp"ohos:tint="#000000"ohos:viewportHeight="24"ohos:viewportWidth="24"ohos:width="24vp"><pathohos:fillColor="#000000"ohos:pathData="M10,17l5,-5 -5,-5v10z"/>
</vector>
还有一个ic_down_24.xml
<?xml version="1.0" encoding="utf-8"?>
<vectorxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="24vp"ohos:width="24vp"ohos:tint="#000000"ohos:viewportHeight="24"ohos:viewportWidth="24"><pathohos:fillColor="#000000"ohos:pathData="M7,10l5,5 5,-5z"/>
</vector>
下面修改一下ServiceAdapter,代码如下所示:
public class ServiceProvider extends BaseItemProvider {private final List<GattService> serviceList;private final AbilitySlice slice;private OperateCallback operateCallback;public void setOperateCallback(OperateCallback operateCallback) {this.operateCallback = operateCallback;}public ServiceProvider(List<GattService> list, AbilitySlice slice) {this.serviceList = list;this.slice = slice;}@Overridepublic int getCount() {return serviceList == null ? 0 : serviceList.size();}@Overridepublic Object getItem(int position) {if (serviceList != null && position >= 0 && position < serviceList.size()) {return serviceList.get(position);}return null;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic Component getComponent(int position, Component component, ComponentContainer componentContainer) {final Component cpt;ServiceHolder holder;GattService service = serviceList.get(position);if (component == null) {cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_service, null, false);holder = new ServiceHolder(cpt);//将获取到的子组件信息绑定到列表项的实例中cpt.setTag(holder);} else {cpt = component;// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。holder = (ServiceHolder) cpt.getTag();}holder.itemService.setClickedListener(component1 -> {boolean isShow = holder.lcCharacteristics.getVisibility() == Component.VISIBLE;//显示特性列表holder.lcCharacteristics.setVisibility(isShow ? Component.HIDE : Component.VISIBLE);//更换图标VectorElement vectorElement = new VectorElement(slice.getContext(), isShow ? ResourceTable.Graphic_ic_right_24 : ResourceTable.Graphic_ic_down_24);holder.ivState.setBackground(vectorElement);//刷新Item高度,这个很重要,不加会造成内容覆盖。notifyDataSetItemChanged(position);});//加载特性 设置属性回调holder.lcCharacteristics.setItemProvider(new CharacteristicProvider(service.getCharacteristics(), slice, operateCallback));holder.txServiceName.setText(BleUtils.getServiceName(service.getUuid()));holder.txUuid.setText(BleUtils.getShortUUID(service.getUuid()));return cpt;}/*** 用于保存列表项的子组件信息*/public static class ServiceHolder {DependentLayout itemService;Text txServiceName;Text txUuid;Image ivState;ListContainer lcCharacteristics;public ServiceHolder(Component component) {itemService = (DependentLayout) component.findComponentById(ResourceTable.Id_item_service);txServiceName = (Text) component.findComponentById(ResourceTable.Id_tx_service_name);txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);ivState = (Image) component.findComponentById(ResourceTable.Id_iv_state);lcCharacteristics = (ListContainer) component.findComponentById(ResourceTable.Id_lc_characteristics);}}
}
和之前的区别就在于构造的时候增加了一个回调,并且在getComponent()
方法中就处理了服务Item的点击事件,而不是像之前一样回调到页面中,在服务Item的点击事件中判断是否显示特性列表同时修改图标资源。最后再将接口回调到页面中。
另外还需要注意一点,那就是Image我在xml中是设置的背景,而不是图片资源,因为在java代码中无法设置矢量图的资源,所以我就改成使用背景资源,需要注意的是背景资源要设置Image具体的大小,否则不会显示,在Java代码中通过VectorElement
来加载矢量图资源,然后设置背景。同时notifyDataSetItemChanged(position)
这样代码也很重要,因为我们的服务Item实际上有两部分内容,服务本身内容和特性列表内容,默认情况下显示服务内容,当点击服务Item时显示特性列表内容,此时如果你按照Android的习惯去搞,你就会发现,Item展开的内容会被其他Item遮挡,所以我们需要加上这样一行代码,让Item进行刷新,这样就不会被其他Item遮挡了。
最后我们在MainAbilitySlice中添加serviceProvider.setOperateCallback(this);
,然后再实现接口
重写里面的onPropertyOperate()
方法。这个方法在下一篇文章中会用到。
@Overridepublic void onPropertyOperate(GattCharacteristic characteristic, String operateName) {}
运行一下看看效果。
六、源码
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:HarmonyBle-Java