继续下一步。
扫描低功耗蓝牙,首先要有触发的地方,其次要有显示结果的地方,这些都需要进行UI的处理,那么下面进行布局的修改和增加,修改activity_main.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“.MainActivity”>
<androidx.recyclerview.widget.RecyclerView
android:id=“@+id/rv_device”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_above=“@+id/btn_start_scan”
android:overScrollMode=“never” />
<com.google.android.material.button.MaterialButton
android:id=“@+id/btn_start_scan”
android:layout_width=“match_parent”
android:layout_height=“50dp”
android:layout_above=“@+id/btn_stop_scan”
android:layout_margin=“6dp”
android:insetTop=“0dp”
android:insetBottom=“0dp”
android:text=“开始扫描” />
<com.google.android.material.button.MaterialButton
android:id=“@+id/btn_stop_scan”
android:layout_width=“match_parent”
android:layout_height=“50dp”
android:layout_alignParentBottom=“true”
android:layout_margin=“6dp”
android:insetTop=“0dp”
android:insetBottom=“0dp”
android:text=“停止扫描” />
下面进行列表item的布局编写,在layout下新建一个item_device_rv.xml文件,文件代码如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:background=“@color/white”
android:foreground=“?attr/selectableItemBackground”
android:orientation=“vertical”>
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:gravity=“center_vertical”
android:padding=“16dp”>
<ImageView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:src=“@drawable/ic_bluetooth_blue” />
<LinearLayout
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_weight=“1”
android:orientation=“vertical”
android:paddingStart=“12dp”>
<TextView
android:id=“@+id/tv_device_name”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:ellipsize=“end”
android:singleLine=“true”
android:text=“设备名称”
android:textColor=“@color/black”
android:textSize=“16sp” />
<TextView
android:id=“@+id/tv_mac_address”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginTop=“8dp”
android:ellipsize=“end”
android:singleLine=“true”
android:text=“Mac地址” />
<TextView
android:id=“@+id/tv_rssi”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“信号强度” />
<View
android:layout_width=“match_parent”
android:layout_height=“0.5dp”
android:background=“#EEE” />
这里面有一个图标,使用路径绘制的ic_bluetooth_blue.xml,放在drawable文件夹下,代码如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><vector xmlns:android=“http://schemas.android.com/apk/res/android”
android:width=“36dp”
android:height=“36dp”
android:autoMirrored=“true”
android:tint=“#42A5F5”
android:viewportWidth=“24.0”
android:viewportHeight=“24.0”>
<path
android:fillColor=“@android:color/white”
android:pathData=“M14.58,12.36l1.38,1.38c0.28,0.28 0.75,0.14 0.84,-0.24c0.12,-0.48 0.18,-0.99 0.18,-1.5c0,-0.51 -0.06,-1.01 -0.18,-1.48c-0.09,-0.38 -0.56,-0.52 -0.84,-0.24l-1.39,1.38C14.39,11.85 14.39,12.17 14.58,12.36zM18.72,7.51l-0.05,0.05c-0.25,0.25 -0.3,0.62 -0.16,0.94c0.47,1.07 0.73,2.25 0.73,3.49c0,1.24 -0.26,2.42 -0.73,3.49c-0.14,0.32 -0.09,0.69 0.16,0.94l0,0c0.41,0.41 1.1,0.29 1.35,-0.23c0.63,-1.3 0.98,-2.76 0.98,-4.3c-0.01,-1.48 -0.34,-2.89 -0.93,-4.16C19.83,7.22 19.13,7.1 18.72,7.51zM15,7l-4.79,-4.79C10.07,2.07 9.89,2 9.71,2h0C9.32,2 9,2.32 9,2.71v6.88L5.12,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0l0,0c-0.39,0.39 -0.39,1.02 0,1.41L8.59,12l-4.89,4.89c-0.39,0.39 -0.39,1.02 0,1.41h0c0.39,0.39 1.02,0.39 1.41,0L9,14.41v6.88C9,21.68 9.32,22 9.71,22h0c0.19,0 0.37,-0.07 0.5,-0.21L15,17c0.39,-0.39 0.39,-1.02 0,-1.42L11.41,12L15,8.42C15.39,8.03 15.39,7.39 15,7zM11,5.83l1.88,1.88L11,9.59V5.83zM12.88,16.29L11,18.17v-3.76L12.88,16.29z” />
好了,现在针对于这个布局方面的内容告一段落,下面先运行一下了:
进行下一步操作。
先进行页面的初始化。新增一个initView的方法。
private static final String TAG = MainActivity.class.getSimpleName();
/**
- nordic扫描回调
*/
private ScanCallback scanCallback;
/**
- 初始化
*/
private void initView() {
RecyclerView rvDevice = findViewById(R.id.rv_device);
findViewById(R.id.btn_start_scan).setOnClickListener(v -> startScanDevice());
findViewById(R.id.btn_stop_scan).setOnClickListener(v -> stopScanDevice());
//扫描结果回调
scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, @NonNull ScanResult result) {
Log.d(TAG, “name:” + result.getDevice().getName() + “,rssi:” + result.getRssi());
}
@Override
public void onScanFailed(int errorCode) {
throw new RuntimeException(“Scan error”);
}
};
}
这个initView主要是页面的初始化,列表在后面进行配置,根据扫描结果来定,然后就是配置扫描回调,这里注意导包的问题,不要到错了包。
然后还有一个开始扫描和停止扫描的方法。
/**
- 开始扫描设备
*/
public void startScanDevice() {
BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
scanner.startScan(scanCallback);
}
/**
- 停止扫描设备
*/
public void stopScanDevice() {
BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
scanner.stopScan(scanCallback);
}
下面在onCreate方法中调用initView()方法。
下面就可以开始运行了。运行之后点击开始扫描按钮,就会扫描附近的低功耗蓝牙设备,(请在附近有已打开低功耗蓝牙时进行扫描)可以在日志栏处进行打印。
这里很明显,扫描到了一些蓝牙设备,并且很多设备没有设备名称。既然有了结果,那么下面就是将扫描到的结果显示在列表上,这样才更直观。
下面将扫描结果渲染到列表上,首先明确列表要显示扫描设备的那些信息,从item来看有设备名、Mac地址、信号强度。那么可以根据这一个扫描的信息构建一个设备类,新建一个BleDevice类,代码如下:
package com.llw.bledemo.bean;
import android.bluetooth.BluetoothDevice;
/**
-
@author llw
-
@description BleDevice
-
@date 2021/7/21 19:20
*/
public class BleDevice {
private BluetoothDevice device;
private int rssi;
private String realName;//真实名称
/**
-
构造Device
-
@param device 蓝牙设备
-
@param rssi 信号强度
-
@param realName 真实名称
*/
public BleDevice(BluetoothDevice device, int rssi, String realName) {
this.device = device;
this.rssi = rssi;
this.realName = realName;
}
public BluetoothDevice getDevice(){
return device;
}
public int getRssi(){
return rssi;
}
public void setRssi(int rssi) {
this.rssi = rssi;
}
public String getRealName(){
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
@Override
public boolean equals(Object object) {
if(object instanceof BleDevice){
final BleDevice that =(BleDevice) object;
return device.getAddress().equals(that.device.getAddress());
}
return super.equals(object);
}
}
下面来写这个适配器,新建一个BleDeviceAdapter类,代码如下:
package com.llw.bledemo.adapter;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseViewHolder;
import com.llw.bledemo.R;
import com.llw.bledemo.bean.BleDevice;
import java.util.List;
/**
-
@author llw
-
@description BleDeviceAdapter
-
@date 2021/7/21 19:34
*/
public class BleDeviceAdapter extends BaseQuickAdapter<BleDevice, BaseViewHolder> {
public BleDeviceAdapter(int layoutResId, List data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder holder, BleDevice bleDevice) {
holder.setText(R.id.tv_device_name, bleDevice.getRealName())
.setText(R.id.tv_mac_address, bleDevice.getDevice().getAddress())
.setText(R.id.tv_rssi, bleDevice.getRssi() + " dBm");
}
}
下面回到MainActivity中对列表进行适配,先定义变量
/**
- 设备列表
*/
private List mList = new ArrayList<>();
/**
- 列表适配器
*/
private BleDeviceAdapter deviceAdapter;
然后在initView方法中进行列表配置,代码如下:
//列表配置
deviceAdapter = new BleDeviceAdapter(R.layout.item_device_rv, mList);
rvDevice.setLayoutManager(new LinearLayoutManager(this));
//启用动画
deviceAdapter.setAnimationEnable(true);
//设置动画方式
deviceAdapter.setAnimationWithDefault(BaseQuickAdapter.AnimationType.SlideInRight);
rvDevice.setAdapter(deviceAdapter);
添加位置如下:
下面就是将扫描结果添加到列表中了,可以写一个方法addDeviceList(),代码如下:
/**
-
添加到设备列表
-
@param bleDevice 蓝牙设备
*/
private void addDeviceList(BleDevice bleDevice) {
if (!mList.contains(bleDevice)) {
bleDevice.setRealName(bleDevice.getRealName() == null ? “UNKNOWN” : bleDevice.getRealName());
mList.add(bleDevice);
} else {
//更新设备信号强度值
for (BleDevice device : mList) {
device.setRssi(bleDevice.getRssi());
}
}
//刷新列表适配器
deviceAdapter.notifyDataSetChanged();
}
然后在扫描的回调中进行调用即可。
点击开始的时候清理一下列表。
下面运行一下:
增加一个表示搜索的效果,在activity_main.xml中增加
<androidx.core.widget.ContentLoadingProgressBar
android:id=“@+id/loading_progress_bar”
style=“?android:attr/progressBarStyleHorizontal”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:indeterminate=“true”
android:indeterminateTint=“@color/purple_200”
android:visibility=“invisible”
tools:ignore=“UnusedAttribute”/>
然后这个进度条设置在列表的上面。
回到MainActivity,创建变量:
/**
- 加载进度条
*/
private ContentLoadingProgressBar loadingProgressBar;
绑定视图
控制视图
运行一下:
连接Ble设备其实也很简单,难的是连接之外的东西,先来构想一下连接功能的业务逻辑,点击设备列表中的设备,进行连接,先显示一个加载布局,表示现在正在连接,然后停止扫描,在根据设备的mac地址去连接这个设备,然后在连接设备的回调中处理连接设备的结果。嗯,就是这样。下面来编码,首先是加载布局的问题。在activity_main.xml中增加如下布局代码:
<LinearLayout
android:id=“@+id/lay_connecting_loading”
android:layout_centerInParent=“true”
android:layout_width=“160dp”
android:layout_height=“160dp”
android:orientation=“vertical”
android:visibility=“invisible”
android:background=“@color/white”
android:gravity=“center”>
<ProgressBar
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:indeterminate=“true”
android:indeterminateTint=“@color/purple_200” />
<TextView
android:layout_marginTop=“12dp”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“连接中…”
android:textColor=“@color/black”
android:textSize=“@dimen/sp_14” />
添加位置如下图
然后在MainActivity中创建变量
/**
- 等待连接
*/
private LinearLayout layConnectingLoading;
绑定视图
下面新增一个方法,用来连接设备。在点击设备列表Item的时候调用。
/**
-
连接设备
-
@param bleDevice 蓝牙设备
*/
private void connectDevice(BleDevice bleDevice) {
//显示连接等待布局
layConnectingLoading.setVisibility(View.VISIBLE);
//停止扫描
stopScanDevice();
//获取远程设备
BluetoothDevice device = bleDevice.getDevice();
//连接gatt
device.connectGatt(this, false, new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED://连接成功
Log.d(TAG,“连接成功”);
runOnUiThread(() -> {
layConnectingLoading.setVisibility(View.GONE);
showMsg(“连接成功”);
});
break;
case BluetoothProfile.STATE_DISCONNECTED://断开连接
Log.d(TAG,“断开连接”);
runOnUiThread(() -> {
layConnectingLoading.setVisibility(View.GONE);
showMsg(“断开连接”);
});
break;
default:
break;
}
}
});
}
在initView()中设置列表点击。
//item点击事件
deviceAdapter.setOnItemClickListener((adapter, view, position) -> {
//连接设备
connectDevice(mList.get(position));
});
OK,下面运行一下:
这个布局背景是白色的不是很明显,改一下好了。在drawable文件夹下新建一个shape_loading_bg.xml,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?><stroke
android:width=“1dp”
android:color=“@color/purple_500” />
然后设置到这个布局中
运行看看
嗯,还可以,就这样了。
有连接设备就自然有断开连接设备。再增加两个变量
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
d:width=“1dp”
android:color=“@color/purple_500” />
然后设置到这个布局中
运行看看
嗯,还可以,就这样了。
有连接设备就自然有断开连接设备。再增加两个变量
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-gcAgWZbt-1714940027123)]
[外链图片转存中…(img-vGw58Q3C-1714940027124)]
[外链图片转存中…(img-pdltrs8C-1714940027124)]
[外链图片转存中…(img-iqw9G2Ro-1714940027124)]
[外链图片转存中…(img-0VtFv2TF-1714940027124)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!