Kotlin版 蓝牙开发 (扫描设备、绑定、解绑)
前言
之前写了一个蓝牙的小Demo,看的人还是有一些的,也有人私信我说,在学Kotlin,能不能出一版Kotlin的博客讲述这个蓝牙开发,这个想法还是不错的,不过就怕写了没有人看,因为在国内Kotlin是受众群体确实比较少,当然了也是有大的方向在往这边推动的,但是小公司依然不会去用Kotlin,如果你看不惯我这个说法也不要告诉我。我只是把这个博客当成是笔记而已,如果能在写作的时候帮助到别人也是乐意的,不能就自勉吧。
运行效果图
扫描蓝牙设备
如果你对上面的效果图感觉满意,那么可以往下面看了,不满意就不浪费你查看其它文章的时间了。
正文
当然还是新创建一个项目,名为MyBluetooth-Kotlin,为了区分我之前写的Java版的项目。
选择语言为Kotlin,然后点击Finish完成项目的创建。第一次创建Kotlin项目花费的时间会比较长,耐心等待。
创建好了之后你看到的第一个页面应该是这样的。
没错,这就是Kotlin语言的Android项目,和Java还是有区别的,建议了解了Kotlin的语法再看文章会比较好,当然你也可以对照我写的Android 蓝牙开发(扫描设备、绑定、解绑)Java版来看,我尽量保持差不多的业务逻辑流程来编写项目。
① 配置项目
在工程的build.gradle中,添加如下依赖
maven { url "https://jitpack.io" }
添加位置如下图所示:
然后是在app下的build.gradle中添加依赖库
compileOptions {//指定使用的JDK1.8 sourceCompatibility = 1.8 targetCompatibility = 1.8 }
//Google Material控件,以及迁移到AndroidX下一些控件的依赖 implementation 'com.google.android.material:material:1.2.0' //RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余 implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30' //权限请求框架 implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation "io.reactivex.rxjava2:rxjava:2.0.0"
添加位置如下图所示:
现在工程和app模块的build.gradle就修改完成了,直接Sync同步一下,同步之后你添加的依赖库才能使用。
然后配置AndroidManifest.xml文件
<!--蓝牙连接权限--> <uses-permission android:name="android.permission.BLUETOOTH" /> <!--蓝牙通讯权限--> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!--位置信息 获取精准位置--> <!--Android 6.0及后续版本,使用蓝牙扫描,还需要添加如下的权限,且该权限还需要在使用时动态申请--> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
添加位置如下图所示:
然后改动colors.xml中系统默认的颜色
这两个颜色会影响到你的状态栏。
然后是styles.xml文件
上面不涉及到代码,所以Java和Kotlin中的资源文件配置是差不多的。
② 布局和样式
图片资源
当然里面的一些其他的图标请到我的源码里面去拿,我就不一一贴出来了
在drawable下创建一个名为progressbar.xml的样式文件,代码如下:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <rotate android:drawable="@drawable/icon_loading" android:fromDegrees="0.0" android:pivotX="50.0%" android:pivotY="50.0%" android:toDegrees="360.0" /> <!-- 其中360.0值越大,转的圈圈越快 --> <span style="white-space:pre" /> </item> </layer-list>
修改activity_main.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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical" tools:context=".MainActivity"> <!--标题--> <androidx.appcompat.widget.Toolbar android:elevation="3dp" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="我的蓝牙" android:textColor="#000" android:textSize="18sp" /> </androidx.appcompat.widget.Toolbar> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#EEEEEE" /> <!--加载布局--> <LinearLayout android:id="@+id/loading_lay" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:visibility="gone"> <ProgressBar android:layout_width="@dimen/dp_40" android:layout_height="@dimen/dp_40" android:indeterminate="true" android:indeterminateDrawable="@drawable/progressbar" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="扫描中..." /> </LinearLayout> <!--设备展示列表--> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv" android:background="#FFF" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#EEEEEE" /> <!--扫描蓝牙--> <TextView android:id="@+id/scan_devices" android:layout_width="match_parent" android:layout_height="50dp" android:background="?android:attr/selectableItemBackground" android:gravity="center" android:text="扫描蓝牙" /> </LinearLayout>
在layout下创建列表展示的item的布局文件,名为item_device_list.xml
代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:id="@+id/item_device" android:background="?android:attr/selectableItemBackground" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:gravity="center_vertical" android:padding="12dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_device_type" android:src="@mipmap/icon_bluetooth" android:layout_width="30dp" android:layout_height="30dp"/> <TextView android:id="@+id/tv_name" android:paddingLeft="12dp" android:textSize="16sp" android:text="设备名称" android:textColor="#000" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content"/> <TextView android:gravity="right" android:id="@+id/tv_bond_state" android:text="绑定状态" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content"/> </LinearLayout> <View android:background="#EBEBEB" android:layout_marginLeft="54dp" android:layout_width="match_parent" android:layout_height="1dp"/> </LinearLayout>
③ 编码
一切准备工作都已经就绪了,下面就进入编码环节,前面的内容其实和Kotlin的关系都不大,下面上正菜,Kotlin相比于Java来说的优势就是简洁,这一点会在下面的编码过程中体现。
1. 通知栏样式修改
首先修改状态栏的文字颜色,如果你现在运行这个项目在手机上时,你会发现状态栏是白色的背景以及白色的文字。如下图所示:
这样的用户体验是很不好的,而在Android6.0以后支持设置高亮状态栏样式。在之前我写Java版的时候特别弄了一个工具类,里面有针对性状态栏的一些样式和颜色改动,但实际上我只用了其中的一个方法,为了一个方法而去写一个工具类显然多此一举了。所以在Kotlin中我想到了更简单的办法,直接在MainActivity中修改状态栏样式。
代码如下:
//设置亮色状态栏模式 systemUiVisibility在Android11中弃用了,可以尝试一下。 window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
放在onCreate方法中,然后运行。
是不是立竿见影,这个效果一行代码解决问题还不用写工具类,完全调用系统的方法,请注意我是Android10.0版本的手机,也是我自己用的手机。
下面写列表的适配器,因为你扫描蓝牙肯定会是一个列表,既然是一个列表那么肯定要有适配器。
2. 蓝牙设备列表适配器编写
创建一个adapter包,包下创建一个DeviceAdapter.kt文件,如下所示
DeviceAdapter的代码如下:
package com.llw.bluetooth.adapter import android.bluetooth.BluetoothClass import android.bluetooth.BluetoothClass.Device.* import android.bluetooth.BluetoothClass.Device.Major.* import android.bluetooth.BluetoothDevice import android.widget.ImageView import android.widget.TextView import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.BaseViewHolder import com.llw.bluetooth.R /** * 蓝牙设备适配器 */ class DeviceAdapter(layoutResId: Int, data: MutableList<BluetoothDevice>?) : BaseQuickAdapter<BluetoothDevice, BaseViewHolder>(layoutResId, data) { override fun convert(helper: BaseViewHolder?, item: BluetoothDevice?) { val tvName = helper!!.getView<TextView>(R.id.tv_name) val icon = helper.getView<ImageView>(R.id.iv_device_type) //根据设备类型设置图标 getDeviceType(item!!.bluetoothClass.majorDeviceClass, icon) tvName.text = if (item.name == null) "无名" else item.name //蓝牙设备绑定状态判断 val tvState = helper!!.getView<TextView>(R.id.tv_bond_state) tvState.text = when (item.bondState) { 10 -> "未配对" 11 -> "正在配对..." 12 -> "已配对" else -> "未配对" } //添加item点击事件 helper.addOnClickListener(R.id.item_device) } /** * 根据类型设置图标 * @param type 类型码 * @param icon 图标 */ private fun getDeviceType(type: Int, icon: ImageView) { when (type) { AUDIO_VIDEO_HEADPHONES,//耳机 AUDIO_VIDEO_WEARABLE_HEADSET,//穿戴式耳机 AUDIO_VIDEO_HANDSFREE,//蓝牙耳机 AUDIO_VIDEO //音频设备 -> icon.setImageResource(R.mipmap.icon_headset) COMPUTER //电脑 -> icon.setImageResource(R.mipmap.icon_computer) PHONE //手机 -> icon.setImageResource(R.mipmap.icon_phone) HEALTH //健康类设备 -> icon.setImageResource(R.mipmap.icon_health) AUDIO_VIDEO_CAMCORDER, //照相机录像机 AUDIO_VIDEO_VCR //录像机 -> icon.setImageResource(R.mipmap.icon_vcr) AUDIO_VIDEO_CAR_AUDIO //车载设备 -> icon.setImageResource(R.mipmap.icon_car) AUDIO_VIDEO_LOUDSPEAKER //扬声器 -> icon.setImageResource(R.mipmap.icon_loudspeaker) AUDIO_VIDEO_MICROPHONE //麦克风 -> icon.setImageResource(R.mipmap.icon_microphone) AUDIO_VIDEO_PORTABLE_AUDIO //打印机 -> icon.setImageResource(R.mipmap.icon_printer) AUDIO_VIDEO_SET_TOP_BOX //音频视频机顶盒 -> icon.setImageResource(R.mipmap.icon_top_box) AUDIO_VIDEO_VIDEO_CONFERENCING //音频视频视频会议 -> icon.setImageResource(R.mipmap.icon_meeting) AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER //显示器和扬声器 -> icon.setImageResource(R.mipmap.icon_tv) AUDIO_VIDEO_VIDEO_GAMING_TOY //游戏 -> icon.setImageResource(R.mipmap.icon_game) AUDIO_VIDEO_VIDEO_MONITOR //可穿戴设备 -> icon.setImageResource(R.mipmap.icon_wearable_devices) else -> icon.setImageResource(R.mipmap.icon_bluetooth) } } /** * 刷新适配器 */ fun changeBondDevice() { notifyDataSetChanged() } }
代码讲解:
class DeviceAdapter(layoutResId: Int, data: MutableList<BluetoothDevice>?) : BaseQuickAdapter<BluetoothDevice, BaseViewHolder>(layoutResId, data)
首先看这个类,在Kotlin继承和实现都是通过 :(英文下的冒号)来操作的,而Java中继承是extends,实现是implements。在上面的代码中DeviceAdapter继承了BaseQuickAdapter,这一点和Java的相似,如下图所示
而Kotlin的语法可以让你把构造方法的参数作为类参数使用,这样解释不知道是不是对的,这里传了一个布局id和数据源。
然后重写里面convert方法
override fun convert(helper: BaseViewHolder?, item: BluetoothDevice?) { val tvName = helper!!.getView<TextView>(R.id.tv_name) val icon = helper.getView<ImageView>(R.id.iv_device_type) getDeviceType(item!!.bluetoothClass.majorDeviceClass, icon) tvName.text = if (item.name == null) "无名" else item.name //蓝牙设备绑定状态判断 val tvState = helper!!.getView<TextView>(R.id.tv_bond_state) tvState.text = when (item.bondState) { 10 -> "未配对" 11 -> "正在配对..." 12 -> "已配对" else -> "未配对" } //添加item点击事件 helper.addOnClickListener(R.id.item_device) }
在代码中你会看到 !! 和 ? 这个你就不明所以了,因为Java中是没有的,这里解释一下,首先是Kotlin对于空安全做了处理, !! 表示当前对象不会空的情况下执行,而 ? 表示当前对象可以为空。
val tvName = helper!!.getView<TextView>(R.id.tv_name) val icon = helper.getView<ImageView>(R.id.iv_device_type)
而这两行代码,可以看到,第一行我给了!!,第二行没有给,这是因为在Kotlin中只要一开始做了处理之后后面就可以不用再次处理,当然你加上!!也没有问题。val 表示不可变量,而通过Kotlin的类型推导机制,tvName此时代表的就是一个通过R.id.tv_name实例化之后的TextView。
//根据设备类型设置图标 getDeviceType(item!!.bluetoothClass.majorDeviceClass, icon)
这行代码调用getDeviceType方法,传入两个参数,这两个参数都已经做了非空的处理,所以在getDeviceType的里面就不用在做空处理了。下面看这个方法的代码:
/** * 根据类型设置图标 * @param type 类型码 * @param icon 图标 */ private fun getDeviceType(type: Int, icon: ImageView) { when (type) { AUDIO_VIDEO_HEADPHONES,//耳机 AUDIO_VIDEO_WEARABLE_HEADSET,//穿戴式耳机 AUDIO_VIDEO_HANDSFREE,//蓝牙耳机 AUDIO_VIDEO //音频设备 -> icon.setImageResource(R.mipmap.icon_headset) COMPUTER //电脑 -> icon.setImageResource(R.mipmap.icon_computer) PHONE //手机 -> icon.setImageResource(R.mipmap.icon_phone) HEALTH //健康类设备 -> icon.setImageResource(R.mipmap.icon_health) AUDIO_VIDEO_CAMCORDER, //照相机录像机 AUDIO_VIDEO_VCR //录像机 -> icon.setImageResource(R.mipmap.icon_vcr) AUDIO_VIDEO_CAR_AUDIO //车载设备 -> icon.setImageResource(R.mipmap.icon_car) AUDIO_VIDEO_LOUDSPEAKER //扬声器 -> icon.setImageResource(R.mipmap.icon_loudspeaker) AUDIO_VIDEO_MICROPHONE //麦克风 -> icon.setImageResource(R.mipmap.icon_microphone) AUDIO_VIDEO_PORTABLE_AUDIO //打印机 -> icon.setImageResource(R.mipmap.icon_printer) AUDIO_VIDEO_SET_TOP_BOX //音频视频机顶盒 -> icon.setImageResource(R.mipmap.icon_top_box) AUDIO_VIDEO_VIDEO_CONFERENCING //音频视频视频会议 -> icon.setImageResource(R.mipmap.icon_meeting) AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER //显示器和扬声器 -> icon.setImageResource(R.mipmap.icon_tv) AUDIO_VIDEO_VIDEO_GAMING_TOY //游戏 -> icon.setImageResource(R.mipmap.icon_game) AUDIO_VIDEO_VIDEO_MONITOR //可穿戴设备 -> icon.setImageResource(R.mipmap.icon_wearable_devices) else -> icon.setImageResource(R.mipmap.icon_bluetooth) } }
其实就是条件分支,根据不同的条件做不同的事情,在Java中使用switch/case,而在Kotlin中使用when。when的语法结构更加的简洁明了,通过 -> 代替了 : ,冒号前面是条件,冒号后面是执行业务。而当多个条件对应一个执行业务时,条件之间用英文逗号隔开,一行代码完成一个条件分支,很简洁,但是不要忘了加上else,这是标准写法,你不加也没事,就如同你写switch/case不加default一样。相信这么一解释你已经理解了when的基本用法了,当然还有很多其他的用法由于业务的原因无法展示,自行百度吧。
tvName.text = if (item.name == null) "无名" else item.name
这行代码等同于
if (item.getName() == null) { helper.setText(R.id.tv_name, "无名"); } else { helper.setText(R.id.tv_name, item.getName()); }
这么一看是不是觉得Kotlin的语法很简单,它允许你的返回值一致的判断进行直接赋值,比如这里判断设备名称为空则显示无名二字,不为空则显示设备名,这两个返回都是String类型,而tvName.text设置的就是String类型,所以就有了上面的简洁代码,有点像三目运算符。
//蓝牙设备绑定状态判断 val tvState = helper!!.getView<TextView>(R.id.tv_bond_state) tvState.text = when (item.bondState) { 10 -> "未配对" 11 -> "正在配对..." 12 -> "已配对" else -> "未配对" } //添加item点击事件 helper.addOnClickListener(R.id.item_device)
这几行代码也没有什么好讲解的了,都讲过了,这也是when的另一种使用方法,可以直接赋值使用。
/** * 刷新适配器 */ fun changeBondDevice() { notifyDataSetChanged() }
最后一个方法就是刷新适配器,当页面的数据有变动是及时刷新。好了这个适配器就讲完了,应该够详细了吧。
3. 权限请求
不管你是用的什么语言来开发Android,你都得遵守Android制定的规则,因此也是要做Android版本大于6.0时动态请求权限。
于是就有了如下这个方法
/** * 检查Android版本 */ private fun checkVersion() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //6.0或6.0以上 permissionsRequest() //动态权限申请 } else { //6.0以下 initBlueTooth() //初始化蓝牙配置 } }
记得在OnCreate中调用喔!
然后来看看这个权限请求的方法
/** * 动态权限申请 */ private fun permissionsRequest() { val rxPermissions = RxPermissions(this) rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION) .subscribe { if (it) { initBlueTooth() } else { showMsg("权限未开启") } } }
showMsg方法
/** * 显示提示消息 */ private fun showMsg(llw: String) { Toast.makeText(this, llw, Toast.LENGTH_SHORT).show() }
这个方法的代码很好理解,和Java的逻辑如出一辙,无非就是不知道这个it是什么意思,it就是它本身的意思,结合方法中的逻辑来看就容易理解了,权限请求自然会有两种结果,同意和不同意,也就是结果是true和false的结果,而这个it就代表这个请求的结果。
4. 初始化蓝牙
首先声明一些成员变量,这里用的是MutableList,表示可变列表,可以有很多方法。
//蓝牙广播接收器 private var bluetoothReceiver: BluetoothReceiver? = null //蓝牙适配器 private var bluetoothAdapter: BluetoothAdapter? = null //蓝牙设备适配器 private var mAdapter: DeviceAdapter? = null //可变列表 private var list: MutableList<BluetoothDevice> = mutableListOf() //请求码 private val REQUEST_ENABLE_BLUETOOTH = 1
BluetoothReceiver报红没有关系不要慌,下面会写的,先看这个初始化蓝牙的方法,比较简单,我想不用讲代码了。
/** * 初始化蓝牙 */ private fun initBlueTooth() { var intentFilter = IntentFilter() intentFilter.addAction(BluetoothDevice.ACTION_FOUND) //获得扫描结果 intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED) //绑定状态变化 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) //开始扫描 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) //扫描结束 bluetoothReceiver = BluetoothReceiver() //实例化广播接收器 registerReceiver(bluetoothReceiver, intentFilter) //注册广播接收器 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() //获取蓝牙适配器 }
5. 扫描蓝牙
在布局中底部我放了一个TextView,点击之后扫描蓝牙,
<!--扫描蓝牙--> <TextView android:id="@+id/scan_devices" android:layout_width="match_parent" android:layout_height="50dp" android:background="?android:attr/selectableItemBackground" android:gravity="center" android:onClick="scanBluetooth" android:text="扫描蓝牙" />
注意看这一句话
android:onClick="scanBluetooth"
通过在布局中点击触发MainActivity中的方法,在MainActivity中写入,或者Alt+Enter
然后你就会看到这样的一个方法
fun scanBluetooth(view: View) {}
首先想清楚这个里面要做什么?难道仅仅只有扫描蓝牙吗?当然不是,首先要看你的设备是否支持蓝牙,其次蓝牙是否打开,最后才是扫描蓝牙
于是里面的代码就可以这样写
/** * 扫描蓝牙 */ fun scanBluetooth(view: View) { if (bluetoothAdapter != null) { //是否支持蓝牙 if (bluetoothAdapter!!.isEnabled) { //打开 //开始扫描周围的蓝牙设备,如果扫描到蓝牙设备,通过广播接收器发送广播 if (mAdapter != null) { //当适配器不为空时,这时就说明已经有数据了,所以清除列表数据,再进行扫描 list.clear() mAdapter!!.notifyDataSetChanged() } bluetoothAdapter!!.startDiscovery() } else { //未打开 val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) startActivityForResult(intent, REQUEST_ENABLE_BLUETOOTH) } } else { showMsg("你的设备不支持蓝牙") } }
6. 广播接收器
点击扫描蓝牙之后会这行扫描事件,会发送一个广播出去,发送出去了自然要有一个地方来接收,这就是广播接收器,在MainActivity定义一个内部类,通过inner关键字
/** * 广播接收器 */ inner class BluetoothReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { //显示蓝牙设备 BluetoothDevice.ACTION_FOUND -> showDevicesData(context, intent) //当有蓝牙绑定状态发生改变时,刷新列表数据 BluetoothDevice.ACTION_BOND_STATE_CHANGED -> mAdapter?.changeBondDevice() //开始扫描 BluetoothAdapter.ACTION_DISCOVERY_STARTED -> loading_lay.visibility = View.VISIBLE //停止扫描 BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> loading_lay.visibility = View.GONE else -> showMsg("未知") } } }
另外别忘了在页面销毁的时候解注册广播
/** * 销毁 */ override fun onDestroy() { super.onDestroy() //卸载广播接收器 unregisterReceiver(bluetoothReceiver) }
当开始扫描的时候发送ACTION_FOUND,而作为接收方,自然要有相应的处理方法,这个时候注意到showDevicesData(context, intent),通过这个方法显示扫描到的蓝牙设备信息。
7. 显示蓝牙设备信息
/** * 显示蓝牙设备信息 * * @param context 上下文参数 * @param intent 意图 */ private fun showDevicesData(context: Context?, intent: Intent) { //获取已绑定的设备 getBondedDevice() //获取周围蓝牙设备 val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) if (list.indexOf(device) == -1) { //防止重复添加 if (device.name != null) { //过滤掉设备名称为null的设备 list.add(device) } } mAdapter = DeviceAdapter(R.layout.item_device_list, list) rv.layoutManager = LinearLayoutManager(context) rv.adapter = mAdapter //item的点击事件 mAdapter!!.setOnItemChildClickListener { adapter, view, position -> //点击时获取状态,如果已经配对过了就不需要在配对 if (list[position].bondState == BluetoothDevice.BOND_NONE) { createOrRemoveBond(1, list[position]) //开始匹配 } else { showDialog( "确定要取消配对吗?", DialogInterface.OnClickListener { dialog, which -> //取消配对 createOrRemoveBond(2, list[position]) //取消匹配 }) } } }
在这里,有一个可以注意的地方
这句话的意思是,参数未被使用,可以使用_代替。然后来看里面的代码,首先第一句是getBondedDevice()
/** * 获取已绑定设备 */ private fun getBondedDevice() { val pairedDevices = bluetoothAdapter!!.bondedDevices if (pairedDevices.size > 0) { //如果获取的结果大于0,则开始逐个解析 for (device in pairedDevices) { if (list.indexOf(device) == -1) { //防止重复添加 if (device.name != null) { //过滤掉设备名称为null的设备 list.add(device) } } } } }
这里用到了in关键字,使用in来检查一个值是否在一个区间内。与他的代码已经有了注释了,就不过多的解释了。
剩下的代码分为两部分,一部分是数据的处理,点击处理。避免重复添加和添加null的设备进入列表,而点击item,根据绑定状态而定,绑定过蓝牙的点击就是取消绑定,这里调用了一个方法。显示一个弹窗
/** * 弹窗 * * @param dialogTitle 标题 * @param onClickListener 按钮的点击事件 */ private fun showDialog(dialogTitle: String, onClickListener: DialogInterface.OnClickListener) { val builder = AlertDialog.Builder(this) builder.setMessage(dialogTitle) builder.setPositiveButton("确定", onClickListener) builder.setNegativeButton("取消", null) builder.create().show() }
而未绑定的点击绑定,调用createOrRemoveBond方法,也可以说是配对,当你取消绑定是也会调用这个方法,只是传递的类型不同而已。
/** * 创建或者取消匹配 * * @param type 处理类型 1 匹配 2 取消匹配 * @param device 设备 */ private fun createOrRemoveBond(type: Int, device: BluetoothDevice) { var method: Method? = null try { when (type) { 1 -> { method = BluetoothDevice::class.java.getMethod("createBond") method.invoke(device) } 2 -> { method = BluetoothDevice::class.java.getMethod("removeBond") method.invoke(device) list.remove(device) //清除列表中已经取消了配对的设备 } } } catch (e: NoSuchMethodException) { e.printStackTrace() } catch (e: InvocationTargetException) { e.printStackTrace() } catch (e: IllegalAccessException) { e.printStackTrace() } }
到这里,代码就写完了。