Android Ble蓝牙App(三)特性和属性

news2024/11/26 4:53:36

Ble蓝牙App(三)特性使用

  • 前言
  • 正文
    • 一、获取属性列表
    • 二、属性适配器
    • 三、获取特性名称
    • 四、特性适配器
    • 五、加载特性
    • 六、显示特性和属性
    • 七、源码

前言

  在上一篇中我们完成了连接和发现服务两个动作,那么再发现服务之后要做什么呢?发现服务只是让你知道设备有什么服务,可以做什么事情。

在这里插入图片描述

正文

  本篇要做的是显示服务下的特性,首先我们了解一下特性的基本知识。在蓝牙低功耗(BLE)中,特性(Characteristic)是蓝牙设备提供的一种数据单元,用于描述设备的某个属性或功能。特性包含了一系列的属性和值,可以用于读取、写入和通知数据。

BLE特性相关的关键概念和说明:

  1. UUID(Universally Unique Identifier):每个特性都会有一个唯一的UUID,用于标识该特性。
  2. 值(Value):特性包含一个值,可以是字节数组、字符串或其他数据类型。该值代表特性的当前状态或数据内容。
  3. 属性(Properties):特性具有一组属性,包括读、写、通知等。属性决定了可以对特性进行哪些操作。
  4. 读(Read):允许外部设备从特性中读取当前的值。
  5. 写(Write):允许外部设备向特性写入一个新的值。
  6. 通知(Notify):当特性的值发生变化时,可以通过通知方式将新的值发送给订阅该特性的外部设备。
  7. 描述符(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.size

    class ViewHolder(itemView: ItemPropertyBinding) : RecyclerView.ViewHolder(itemView.root) {
        var binding: ItemPropertyBinding

        init {
            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">

    <TextView
        android: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" />

    <TextView
        android: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" />

    <TextView
        android: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" />

    <TextView
        android: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.RecyclerView
        android: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.size

    class ViewHolder(itemView: ItemCharacteristicBinding) : RecyclerView.ViewHolder(itemView.root) {
        var binding: ItemCharacteristicBinding

        init {
            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.ConstraintLayout
        android:id="@+id/item_service"
        android:layout_width="match_parent"
        android:foreground="?attr/selectableItemBackground"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android: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" />

        <TextView
            android: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" />

        <TextView
            android: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" />

        <TextView
            android: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" />

        <ImageView
            android: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.RecyclerView
        android: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">
    <path
        android: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">
    <path
        android: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.size

    class ViewHolder(itemView: ItemServiceBinding) : RecyclerView.ViewHolder(itemView.root) {
        var binding: ItemServiceBinding

        init {
            binding = itemView
        }
    }
}

  和之前的区别就在于构造的时候增加了一个回调,并且在onCreateViewHolder()函数中就处理了服务Item的点击事件,而不是像之前一样回调到Activity中,在服务Item的点击事件中判断是否显示特性列表同时修改图标资源。最后再将接口回调到Activity中。

六、显示特性和属性

  现在要做的就是修改MainActivity中的代码,首先修改activity_main.xml中的代码,主要是修改之前的rv_service中的属性值,修改后如下所示:

    <androidx.recyclerview.widget.RecyclerView
        android: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 = false
                    layoutManager = 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()函数,运行一下看看效果。

在这里插入图片描述

七、源码

如果对你有所帮助的话,不妨 StarFork,山高水长,后会有期~

源码地址:GoodBle

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/844825.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java循环:探索迭代的世界

文章目录 1. for循环2. while循环3. do-while循环总结 循环是编程中的重要概念&#xff0c;它允许我们重复执行特定的代码块&#xff0c;帮助我们简化复杂的任务和提高代码的效率。在Java中&#xff0c;循环有多种形式&#xff0c;包括for循环、while循环和do-while循环。本篇博…

玩转SWAT模型——最全面SWAT模型教程【建模方法、实例应用、高级进阶】

目录 第一部分&#xff1a;SWAT模型实践部分 第二部分&#xff1a;SWAT模型【进阶部分】 更多推荐 【专家】&#xff1a;刘老师【副教授】&#xff0c;北京重点高校资深专家&#xff0c;和美国SWAT软件开发方长期合作&#xff0c;拥有丰富的科研及工程技术经验&#xff0c;长…

虫情监测仪介绍—技术原理、功能优势是什么?

KH-CQPest虫情监测仪是做好虫情监测的重要设备&#xff0c;利用虫情监测仪能够对农业大田、智慧温室、林业等场景的害虫分布情况及害虫种类进行监测&#xff0c;协助人们制定合理的防治措施。 1.技术原理&#xff1a; KH-CQPest虫情监测仪采用光学诱虫原理&#xff0c;配合传感…

【嵌入式学习笔记】嵌入式入门3——串口USART

1.数据通信的基础概念 1.1.串行/并行通信 数据通信按数据通信方式分类&#xff1a;串行通信、并行通信 1.2.单工/半双工/全双工通信 数据通信按数据传输方向分类&#xff1a;单工通信、半双工通信、全双工通信 单工通信&#xff1a;数据只能沿一个方向传输半双工通信&…

架构训练营学习笔记:6-2 微服务基础选型

基础选型 微服务基础设施架构 优先级 其中&#xff0c;核心 就是服务注册、服务发现、服务路由。 模式1-嵌入SDK 模式2-反向代理式 模式3-网络代理式&#xff08;Service Mesh&#xff09; 模式对比 常见微服务框架选择 嵌入SDK-dubbo Spring Cloud 反向代理式 APISIX …

【Docker】性能测试监控平台搭建:InfluxDB+Grafana+Jmeter+cAdvisor

前言 在做性能测试时&#xff0c;如果有一个性能测试结果实时展示的页面&#xff0c;可以极大的提高我们对系统性能表现的掌握程度&#xff0c;进而提高我们的测试效率。但是我们每次打开Jmeter都会有几个硕大的字提示别用GUI模式进行负载测试&#xff0c;而且它自带的监视器效…

LeetCode-Java(06)

24. 两两交换链表中的节点 非递归解法 class Solution {public ListNode swapPairs(ListNode head) {ListNode pre new ListNode(0);pre.next head;ListNode temp pre;while(temp.next ! null && temp.next.next ! null) {ListNode start temp.next;ListNode end …

指标体系构建与验证

文章目录 知网论文1 本土化计算思维评价指标体系的构建与探索——基于1410名高中生的样本分析与验证2 企业集团人力资源管理质量评价指标体系的构建3 基于知识管理的创新型企业评价指标体系的构建及验证4 中国公民科学素质测评指标体系研究5 中国企业劳动关系评价指标体系的改进…

Linux ❀ Yum源安装RPM包常见问题与解决方法

文章目录 1、Yum源仓库检查2、执行安装报错2.1 RPM包缺失2.2 进程锁定2.3 未完成事务2.4 RPM包冲突 1、Yum源仓库检查 404错误多为yum源无法访问导致&#xff0c;检查yum仓库是否配置正确&#xff0c;同时确认baseurl连通性。 $ cd /etc/yum.repos.d/ $ cat ${yum_file} | gr…

ETHERCAT转PROFIBUS连接到300plc的配置方法

由于捷米JM-DP-ECT&#xff0c;是自主研发的一款PROFIBUS从站功能的通讯网关&#xff0c;它的主要功能是将ETHERCAT设备接入到PROFIBUS网络中生产环境比较复杂有多个设备采用不同的协议这极大的阻碍了&#xff0c;各个设备的数据互通。 JM-DP-ECT这个小小的网关可不简单&#x…

thunder gbm

文章目录 背景参考官网信息训练调参模型保存推理 背景 想在 GPU 上使用使用闪电般快速的提升方法&#xff1f;了解这个库就好了。在很多任务上&#xff0c;它都比 LightGBM 和 XGBoost 快。 ThunderGBM 的主要特征如下&#xff1a; 通常是其它库的 10 倍。 支持 Python&#x…

记一次数据批量插入实践

背景&#xff1a; 2023月7月份入职新公司&#xff0c;初来乍到还没参入到具体的项目中&#xff0c;技术负责人安排写一个批量处理数据的服务&#xff0c;于是便有了以下文章。 数据流程大概是这样&#xff0c;从clickhouse表中获取数据&#xff0c;并从elasticserach中根据业务…

【VALSE2023】0610 邓成/杨二昆《多模态融合感知年度进展综述》

from&#xff1a; https://www.bilibili.com/video/BV1YP411t73r 文章目录 多模态融合感知理论多模态预训练模型多模态感知理解多模态内容生成 总结和展望 多模态融合感知理论 多模态预训练模型 多模态感知理解 多模态内容生成 总结和展望

flink如何监听kafka主题配置变更

背景&#xff1a; 从前一篇文章我们知道flink消费kafka主题时是采用的手动assign指定分区的方式&#xff0c;这种消费方式是不处理主题的rebalance操作的&#xff0c;也就是消费者组中即使有消费者退出或者进入也是不会触发消费者所消费的分区的&#xff0c;那么疑问就来了&am…

用户的管理

一、用户的管理 useradd、usermod、userdel 二、用户组的管理 groupadd、groupmode、groupdel 三、用户登录情况查看 w可以看用户操作 其他可以看用户ip 四、用户的提权&#xff08;重点&#xff09; 三种方法&#xff1a;1、su - 用户名&#xff08;需要该用户名的密码&a…

Pandas 的Merge函数详解

在日常工作中&#xff0c;我们可能会从多个数据集中获取数据&#xff0c;并且希望合并两个或多个不同的数据集。这时就可以使用Pandas包中的Merge函数。在本文中&#xff0c;我们将介绍用于合并数据的三个函数 merge、 merge_ordered、 merge_asofmerge merge函数是Pandas中…

Kotlin~Mediator中介者模式

概念 创建一个中介来降低对象之间的耦合度&#xff0c;关系”多对多“变为“一对多”。 角色介绍 Mediator&#xff1a;抽象中介者&#xff0c;接口或者抽象类。ConcreteMediator&#xff1a;中介者具体实现&#xff0c;实现中介者接口&#xff0c;定义一个List管理Colleagu…

【频率派和贝叶斯派】进阶学习-贝叶斯方法原理、基本结构、代码构建+图模型

文章目录 前言1.理论支撑贝叶斯思考模式贝叶斯定理贝叶斯公式 2. 应用转化2.1 拼写检查 3. 贝叶斯网络3.1 贝叶斯网络的定义3.2 三个形式和实际案例的构建关系 前言 频率派与贝叶斯派各自不同的思考方式&#xff1a; 1.频率派把需要推断的参数θ看做是固定的未知数&#xff0c…

草图大师su曲面怎么推拉?SketchUp推拉怎么使用?

首先我们需要安装这个推拉插件&#xff0c;然后打开草图大师&#xff0c;打开超级推拉工具栏&#xff0c;可以看到&#xff0c;超级推拉主要有以下几种推拉方式&#xff1a;J联合推拉、R近似值推拉、V矢量推拉、N法线推拉、X挤出推拉、F跟随推拉。 首先选择你要进行推拉操作的物…

【iPhone】手机还有容量,拍视频却提示 iPhone 储存空间已满

文章目录 前言解决方案 结语 前言 今天在用 iPhone 录像的时候突然提醒我 iPhone储存空间已满 你没有足够的储存空间来录制视频” 可我明明还有 20G 的容量 我非常疑惑&#xff0c;因为我之前还剩1个G都能录像&#xff0c;现在20G反而不行了&#xff0c;于是重启了手机&#…