安卓手机与蓝牙模块联合调试(五)-- 编写自己的蓝牙控制界面控制单片

安卓手机与蓝牙模块联合调试(五)-- 编写自己的蓝牙控制界面控制单片机(上篇,Android 代码实现)

2018年09月18日 00:10:43 涛声依旧Cjt 阅读数:252更多

所属专栏: 安卓与蓝牙硬件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010898329/article/details/82424273

(1)安卓手机与蓝牙模块联合调试(一)—— 蓝牙模块的串口通讯 

(2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上)

(3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下)

(4)安卓手机与蓝牙模块联合调试(四)—— 单片机数据上传至蓝牙(STC89C52 + DS18b20)

本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机通过蓝牙与单片机通信-发送指令/接收数据


Github参考:  dingjikerbo/BluetoothKit: Android BLE蓝牙通信库 

                      zagum/Android-SwitchIcon: Google launcher-style implementation of switch (enable/disable) icon

                      huangyanbin/CalendarView: 日历 仪表盘 圆盘,提供全新RecyclerView日历,功能更加强大。

                      Android BLE4.0 常用的一些Service和Characteristic的UUID - CSDN博客
 

1.AS添加依赖的注意事项。

由于许多前辈的开源精神和无私奉献,才使得现如今你的IT事业日新月异,也让我们后来人免去了很多重复造轮子的繁琐工作,因此在这里感谢无私的开源奉献者。

下面说下依赖项目引入时候的注意事项,其实如果自己去git上看也能够自己搞懂。switch-icon的依赖请务必在项目project/build.gradle中添加如下两行,原因是因为该项目放在了jitpack上,使用maven方式。

 

2.看下演示的效果。

 

 

 

3.上代码,本篇就提交部分主要代码,后续会更新到GitHub。

先看下依赖引入,使用了第三方的蓝牙扫描连接和读写库,按钮切换库,自定义的表盘,build.gradle文件如下

 
  1. apply plugin: 'com.android.application'

  2.  
  3. android {

  4. compileSdkVersion 27

  5. defaultConfig {

  6. applicationId "com.cjt.bluetoothscm"

  7. minSdkVersion 15

  8. targetSdkVersion 27

  9. versionCode 1

  10. versionName "1.0"

  11. testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

  12. /*由于使用了Vector Asset,所以必须添加支持*/

  13. vectorDrawables.useSupportLibrary = true

  14. }

  15. buildTypes {

  16. release {

  17. minifyEnabled false

  18. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

  19. }

  20. }

  21. }

  22.  
  23. dependencies {

  24. implementation fileTree(dir: 'libs', include: ['*.jar'])

  25. implementation 'com.android.support:appcompat-v7:27.1.1'

  26. implementation 'com.android.support.constraint:constraint-layout:1.1.3'

  27. implementation 'com.android.support:design:27.1.1'

  28. testImplementation 'junit:junit:4.12'

  29. androidTestImplementation 'com.android.support.test:runner:1.0.2'

  30. androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

  31. /*第三方蓝牙操作库*/

  32. implementation 'com.inuker.bluetooth:library:1.4.0'

  33. /*第三方SwitchIcon,图标切换库*/

  34. implementation 'com.github.zagum:Android-SwitchIcon:1.3.7'

  35. }

由于我在项目中大量使用了Vector Asset图标,所以依赖中要添加对VectorDrawable的支持。还有一个表盘的自定义View,我没有引入依赖,直接拷贝了别人的java代码和资源文件到项目中使用的,稍后也会贴出来部分。

布局全部使用的约束布局,感觉比RelativeLayout还好用,如果有不熟悉的同学,建议可以找相关的教程学习下,刚开始可能不习惯,后面用熟练了会感觉很爽,以前我都是手写布局xml文件,现在有了这个东西,反而喜欢直接拖拽,简单快捷。下面的是activity_main.xml布局文件

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="match_parent"

  7. tools:context=".MainActivity">

  8.  
  9. <TextView

  10. android:id="@+id/main_title"

  11. android:layout_width="0dp"

  12. android:layout_height="wrap_content"

  13. android:layout_marginEnd="8dp"

  14. android:layout_marginLeft="8dp"

  15. android:layout_marginRight="8dp"

  16. android:layout_marginStart="8dp"

  17. android:layout_marginTop="8dp"

  18. android:gravity="center"

  19. android:textSize="18sp"

  20. android:text="@string/scan_hint"

  21. android:textColor="@color/colorAccent"

  22. app:layout_constraintEnd_toEndOf="parent"

  23. app:layout_constraintStart_toStartOf="parent"

  24. app:layout_constraintTop_toTopOf="parent" />

  25.  
  26. <View

  27. android:id="@+id/top_div"

  28. android:layout_width="wrap_content"

  29. android:layout_height="1dp"

  30. android:layout_marginEnd="5dp"

  31. android:layout_marginLeft="5dp"

  32. android:layout_marginRight="5dp"

  33. android:layout_marginStart="5dp"

  34. android:layout_marginTop="10dp"

  35. android:background="@color/arc1"

  36. app:layout_constraintEnd_toEndOf="parent"

  37. app:layout_constraintStart_toStartOf="parent"

  38. app:layout_constraintTop_toBottomOf="@+id/main_title" />

  39.  
  40. <com.cjt.bluetoothscm.DashboardView

  41. android:id="@+id/temp_view"

  42. android:layout_width="280dp"

  43. android:layout_height="280dp"

  44. android:layout_marginTop="15dp"

  45. app:angleTextSize="16sp"

  46. app:layout_constraintEnd_toEndOf="parent"

  47. app:layout_constraintStart_toStartOf="parent"

  48. app:layout_constraintTop_toBottomOf="@+id/top_div" />

  49.  
  50. <View

  51. android:id="@+id/top_div2"

  52. android:layout_width="wrap_content"

  53. android:layout_height="1dp"

  54. android:layout_marginEnd="5dp"

  55. android:layout_marginLeft="5dp"

  56. android:layout_marginRight="5dp"

  57. android:layout_marginStart="5dp"

  58. android:layout_marginTop="20dp"

  59. android:background="@color/arc1"

  60. app:layout_constraintEnd_toEndOf="parent"

  61. app:layout_constraintStart_toStartOf="parent"

  62. app:layout_constraintTop_toBottomOf="@id/temp_view" />

  63.  
  64. <com.github.zagum.switchicon.SwitchIconView

  65. android:id="@+id/sw_lamp_01"

  66. android:layout_width="60dp"

  67. android:layout_height="60dp"

  68. android:layout_marginLeft="20dp"

  69. android:layout_marginStart="20dp"

  70. android:layout_marginTop="20dp"

  71. app:layout_constraintStart_toStartOf="parent"

  72. app:layout_constraintTop_toBottomOf="@+id/top_div2"

  73. app:si_animation_duration="200"

  74. app:si_disabled_alpha=".5"

  75. app:si_disabled_color="@color/colorOff"

  76. app:si_enabled="false"

  77. app:si_no_dash="true"

  78. app:si_tint_color="@color/colorOn"

  79. app:srcCompat="@drawable/ic_lamp" />

  80.  
  81. <com.github.zagum.switchicon.SwitchIconView

  82. android:id="@+id/sw_lamp_02"

  83. android:layout_width="60dp"

  84. android:layout_height="60dp"

  85. android:layout_marginLeft="30dp"

  86. android:layout_marginStart="30dp"

  87. android:layout_marginTop="20dp"

  88. app:layout_constraintStart_toEndOf="@+id/sw_lamp_01"

  89. app:layout_constraintTop_toBottomOf="@+id/top_div2"

  90. app:si_animation_duration="200"

  91. app:si_disabled_alpha=".5"

  92. app:si_disabled_color="@color/colorOff"

  93. app:si_enabled="false"

  94. app:si_no_dash="true"

  95. app:si_tint_color="@color/colorOn"

  96. app:srcCompat="@drawable/ic_lamp" />

  97.  
  98. <com.github.zagum.switchicon.SwitchIconView

  99. android:id="@+id/sw_power"

  100. android:layout_width="60dp"

  101. android:layout_height="60dp"

  102. android:layout_marginLeft="30dp"

  103. android:layout_marginStart="30dp"

  104. android:layout_marginTop="20dp"

  105. app:layout_constraintStart_toEndOf="@+id/sw_lamp_02"

  106. app:layout_constraintTop_toBottomOf="@+id/top_div2"

  107. app:si_animation_duration="200"

  108. app:si_disabled_alpha=".5"

  109. app:si_disabled_color="@color/colorOff"

  110. app:si_enabled="false"

  111. app:si_no_dash="true"

  112. app:si_tint_color="@color/colorOn"

  113. app:srcCompat="@drawable/ic_switch" />

  114.  
  115. <com.github.zagum.switchicon.SwitchIconView

  116. android:id="@+id/sw_fan"

  117. android:layout_width="60dp"

  118. android:layout_height="60dp"

  119. android:layout_marginLeft="30dp"

  120. android:layout_marginStart="30dp"

  121. android:layout_marginTop="20dp"

  122. app:layout_constraintStart_toEndOf="@+id/sw_power"

  123. app:layout_constraintTop_toBottomOf="@+id/top_div2"

  124. app:si_animation_duration="200"

  125. app:si_disabled_alpha=".5"

  126. app:si_disabled_color="@color/colorOff"

  127. app:si_enabled="false"

  128. app:si_no_dash="true"

  129. app:si_tint_color="@color/colorOn"

  130. app:srcCompat="@drawable/ic_fan" />

  131.  
  132. <TextView

  133. android:id="@+id/lamp_01_name"

  134. android:layout_width="60dp"

  135. android:layout_height="wrap_content"

  136. android:layout_marginTop="8dp"

  137. android:gravity="center"

  138. android:text="@string/lamp_01"

  139. app:layout_constraintEnd_toEndOf="@+id/sw_lamp_01"

  140. app:layout_constraintStart_toStartOf="@+id/sw_lamp_01"

  141. app:layout_constraintTop_toBottomOf="@+id/sw_lamp_01"

  142. tools:ignore="HardcodedText" />

  143.  
  144. <TextView

  145. android:id="@+id/lamp_02_name"

  146. android:layout_width="60dp"

  147. android:layout_height="wrap_content"

  148. android:layout_marginTop="8dp"

  149. android:gravity="center"

  150. android:text="@string/lamp_02"

  151. app:layout_constraintEnd_toEndOf="@+id/sw_lamp_02"

  152. app:layout_constraintStart_toStartOf="@+id/sw_lamp_02"

  153. app:layout_constraintTop_toBottomOf="@+id/sw_lamp_02" />

  154.  
  155. <TextView

  156. android:id="@+id/power_name"

  157. android:layout_width="60dp"

  158. android:layout_height="wrap_content"

  159. android:layout_marginTop="8dp"

  160. android:gravity="center"

  161. android:text="@string/power_sw"

  162. app:layout_constraintEnd_toEndOf="@+id/sw_power"

  163. app:layout_constraintStart_toStartOf="@+id/sw_power"

  164. app:layout_constraintTop_toBottomOf="@+id/sw_power" />

  165.  
  166. <TextView

  167. android:id="@+id/fan_name"

  168. android:layout_width="60dp"

  169. android:layout_height="wrap_content"

  170. android:layout_marginTop="8dp"

  171. android:gravity="center"

  172. android:text="@string/fan_sw"

  173. app:layout_constraintEnd_toEndOf="@+id/sw_fan"

  174. app:layout_constraintStart_toStartOf="@+id/sw_fan"

  175. app:layout_constraintTop_toBottomOf="@+id/sw_fan" />

  176.  
  177. </android.support.constraint.ConstraintLayout>

接着贴出来MainActivity.java文件

 
  1. package com.cjt.bluetoothscm;

  2.  
  3. import android.app.Activity;

  4. import android.bluetooth.BluetoothDevice;

  5. import android.content.Intent;

  6. import android.os.Bundle;

  7. import android.support.v7.app.AppCompatActivity;

  8. import android.text.TextUtils;

  9. import android.util.Log;

  10. import android.view.Menu;

  11. import android.view.MenuItem;

  12. import android.view.View;

  13. import android.widget.TextView;

  14. import android.widget.Toast;

  15.  
  16. import com.github.zagum.switchicon.SwitchIconView;

  17. import com.inuker.bluetooth.library.Constants;

  18. import com.inuker.bluetooth.library.connect.options.BleConnectOptions;

  19. import com.inuker.bluetooth.library.connect.response.BleConnectResponse;

  20. import com.inuker.bluetooth.library.connect.response.BleNotifyResponse;

  21. import com.inuker.bluetooth.library.connect.response.BleWriteResponse;

  22. import com.inuker.bluetooth.library.model.BleGattProfile;

  23.  
  24. import java.util.UUID;

  25.  
  26. import static com.inuker.bluetooth.library.Constants.REQUEST_SUCCESS;

  27.  
  28. /*****************

  29. * 包名:com.cjt.bluetoothscm

  30. * 类名:MainActivity.java

  31. * 时间:2018/9/11 23:28

  32. * 作者:Cao Jiangtao

  33. * 首页:https://1989jiangtao.github.io/index.html

  34. ******************/

  35. public class MainActivity extends AppCompatActivity implements View.OnClickListener {

  36.  
  37. private static final int REQUEST_CONNECT_DEVICE = 0x100;

  38.  
  39. TextView mainTitle ;

  40.  
  41. // 温度显示仪表盘

  42. DashboardView tempView ;

  43. private final static int invs[] = {35, 18, 35};

  44. private final static int[] colorRes = {R.color.arc1, R.color.arc2, R.color.arc3};

  45.  
  46. // 灯组01 ,灯组02 , 电源开关, 风扇开关

  47. SwitchIconView lamp01 , lamp02 , powerSw , fanSw ;

  48. TextView lamp01Name , lamp02Name ,powerName , fanName;

  49.  
  50. // 蓝牙通信的地址和两个UUID

  51. String MAC = "" ;

  52. UUID serviceUuid , characterUuid ;

  53.  
  54. @Override

  55. protected void onCreate(Bundle savedInstanceState) {

  56. super.onCreate(savedInstanceState);

  57. setContentView(R.layout.activity_main);

  58.  
  59. initView();

  60.  
  61. // 运行的时候检查是否打开蓝牙,没有打开就开启蓝牙

  62. if(!MyApp.getBluetoothClient().isBluetoothOpened())

  63. MyApp.getBluetoothClient().openBluetooth();

  64.  
  65. }

  66.  
  67. private void initView() {

  68.  
  69. mainTitle = findViewById(R.id.main_title);

  70. tempView = findViewById(R.id.temp_view);

  71. lamp01 = findViewById(R.id.sw_lamp_01);

  72. lamp02 = findViewById(R.id.sw_lamp_02);

  73. powerSw = findViewById(R.id.sw_power);

  74. fanSw = findViewById(R.id.sw_fan);

  75. lamp01Name = findViewById(R.id.lamp_01_name);

  76. lamp02Name = findViewById(R.id.lamp_02_name);

  77. powerName = findViewById(R.id.power_name);

  78. fanName = findViewById(R.id.fan_name);

  79.  
  80. // 为按钮设置点击事件

  81. lamp01.setOnClickListener(this);

  82. lamp02.setOnClickListener(this);

  83. powerSw.setOnClickListener(this);

  84. fanSw.setOnClickListener(this);

  85.  
  86. // 初始化温度表盘

  87. String[] str = getResources().getStringArray(R.array.mult_temp_dash);

  88. tempView.initDash(-20, invs, str, "℃", colorRes);

  89.  
  90. }

  91.  
  92. @Override

  93. public boolean onCreateOptionsMenu(Menu menu) {

  94. getMenuInflater().inflate(R.menu.menu_main , menu); // 加载菜单页面

  95. return super.onCreateOptionsMenu(menu);

  96. }

  97.  
  98. @Override

  99. public boolean onOptionsItemSelected(MenuItem item) {

  100. if(item.getItemId() == R.id.action_scan){

  101. Intent intent = new Intent(MainActivity.this , ScanResultActivity.class);

  102. startActivityForResult(intent , REQUEST_CONNECT_DEVICE);

  103. return true;

  104. }

  105. return super.onOptionsItemSelected(item);

  106. }

  107.  
  108. @Override

  109. protected void onDestroy() {

  110. // 关闭蓝牙

  111. MyApp.getBluetoothClient().closeBluetooth();

  112. super.onDestroy();

  113. }

  114.  
  115. @Override

  116. public void onClick(View v) {

  117.  
  118. // 当蓝牙有连接,并且MAC地址存在,两个UUID都不为空的情况下,点击按钮才有效

  119. // 以下只要有一个条件不满足,就不让点击按钮发送数据

  120. if(!MyApp.getBluetoothClient().isBleSupported()

  121. || TextUtils.isEmpty(MAC)

  122. || TextUtils.isEmpty(serviceUuid.toString())

  123. || TextUtils.isEmpty(characterUuid.toString())){

  124. Toast.makeText(MainActivity.this , "请先检查蓝牙设备与手机是否连接正常",Toast.LENGTH_SHORT).show();

  125. return;

  126. }

  127.  
  128. switch (v.getId()){

  129. case R.id.sw_lamp_01: // 灯组01

  130. lamp01.switchState();

  131. lamp01Name.setText(lamp01.isIconEnabled() ? "灯组1开" : "灯组1关");

  132. writeCmd(MAC , serviceUuid , characterUuid , "001-on\r\n");

  133. break;

  134. case R.id.sw_lamp_02: // 灯组02

  135. lamp02.switchState();

  136. lamp02Name.setText(lamp02.isIconEnabled() ? "灯组1开" : "灯组1关");

  137. break;

  138. case R.id.sw_power: // 电源

  139. powerSw.switchState();

  140. powerName.setText(powerSw.isIconEnabled() ? "电源开" : "电源关");

  141. writeCmd(MAC , serviceUuid , characterUuid , "power-on\r\n");

  142. break;

  143. case R.id.sw_fan: // 风扇

  144. fanSw.switchState();

  145. fanName.setText(fanSw.isIconEnabled() ? "风扇开" : "风扇关");

  146. writeCmd(MAC , serviceUuid , characterUuid , "fan-sw\r\n");

  147. break;

  148. }

  149. }

  150.  
  151. @Override

  152. protected void onActivityResult(int requestCode, int resultCode, Intent data) {

  153. super.onActivityResult(requestCode, resultCode, data);

  154. Log.d("CJT" , "requestCode = "+ requestCode +" , resultCode = "+ resultCode + " , data ="+data);

  155. if(requestCode == REQUEST_CONNECT_DEVICE) {

  156. // 响应结果

  157. switch (resultCode) {

  158.  
  159. case Activity.RESULT_CANCELED:

  160. Toast.makeText(this , "取消了扫描!",Toast.LENGTH_SHORT).show();

  161. break;

  162. case Activity.RESULT_OK:

  163. // 选择连接的设备

  164. final BluetoothDevice device = data.getParcelableExtra(RecycleAdapter.EXTRA_DEVICE);

  165. // 得到选择后传过来的MAC地址

  166. MAC = device.getAddress();

  167. Log.d("CJT" , "address ===================== " +MAC);

  168.  
  169. // 设置BLE设备的连接参数

  170. BleConnectOptions options = new BleConnectOptions.Builder()

  171. .setConnectRetry(3) // 连接如果失败重试3次

  172. .setConnectTimeout(30000) // 连接超时30s

  173. .setServiceDiscoverRetry(3) // 发现服务如果失败重试3次

  174. .setServiceDiscoverTimeout(20000) // 发现服务超时20s

  175. .build();

  176.  
  177. // 开始连接操作

  178. MyApp.getBluetoothClient().connect(MAC, options, new BleConnectResponse() {

  179. @Override

  180. public void onResponse(int code, BleGattProfile data) {

  181. Log.d("CJT" , "getBluetoothClient().connect --- code ----- " + code);

  182.  
  183. // 表示连接成功

  184. if(code == REQUEST_SUCCESS){

  185.  
  186. mainTitle.setText("当前连接设备 :"+device.getName());

  187. // for(BleGattService sls : data.getServices()){

  188. // Log.d("CJT" , "onActivityResult -------1111111111------- : "+sls.getUUID());

  189. // for(BleGattCharacter gls : sls.getCharacters()){

  190. // Log.d("CJT" , "onActivityResult *******22222222222***** : "+gls.getUuid());

  191. // }

  192. // }

  193. serviceUuid = data.getServices().get(3).getUUID();

  194. Log.d("CJT" , "getBluetoothClient().connect --- serviceUuid : "+serviceUuid);

  195. characterUuid = data.getService(serviceUuid).getCharacters().get(0).getUuid();

  196. Log.d("CJT" , "getBluetoothClient().connect --- characterUuid : "+characterUuid);

  197.  
  198. // 获取温度值

  199. getTemperature(MAC , serviceUuid , characterUuid);

  200.  
  201. // 下发数据

  202. writeCmd(MAC , serviceUuid , characterUuid , "finish\r\n");

  203.  
  204. }else{

  205. mainTitle.setText("当前暂无蓝牙设备连接");

  206. Toast.makeText(MainActivity.this , "蓝牙连接不成功!",Toast.LENGTH_SHORT).show();

  207. }

  208. }

  209. });

  210.  
  211. break;

  212. }

  213.  
  214. }

  215. }

  216.  
  217. /***

  218. * 获取温度值并显示到界面上

  219. * @param address 设备地址

  220. * @param serviceUuid 服务UUID

  221. * @param characterUuid 特征UUID

  222. */

  223. private void getTemperature(String address , UUID serviceUuid , UUID characterUuid ){

  224. MyApp.getBluetoothClient().notify(address, serviceUuid, characterUuid, new BleNotifyResponse() {

  225. @Override

  226. public void onNotify(UUID service, UUID character, byte[] value) {

  227. String hexStr = bytesToHexString(value);

  228. int beginIndex = hexStr.indexOf("2b") + 2; // 加号开始截取,并且跳过加号

  229. int endIndex = hexStr.indexOf("2e") + 2 ; // 小数点开始截取

  230. String validTemp = hexStr.substring(beginIndex , endIndex );

  231. Log.d("CJT" , "valid temp = "+validTemp+", hex2Str = "+ new String(hexStringToBytes(validTemp)));

  232.  
  233. // 设置温度值

  234. tempView.setAngleWithAnim(Double.valueOf(new String(hexStringToBytes(validTemp))));

  235. }

  236.  
  237. @Override

  238. public void onResponse(int code) {

  239.  
  240. }

  241. });

  242. }

  243.  
  244. /***

  245. * 向设备下发指令

  246. * @param address 设备MAC地址

  247. * @param serviceUuid 服务UUID

  248. * @param characterUuid 特征UUID

  249. * @param cmd 待下发的命令

  250. */

  251. private void writeCmd(String address , UUID serviceUuid , UUID characterUuid , String cmd){

  252. MyApp.getBluetoothClient().write(address, serviceUuid, characterUuid, cmd.getBytes(), new BleWriteResponse() {

  253. @Override

  254. public void onResponse(int code) {

  255. if(code == Constants.REQUEST_SUCCESS){

  256.  
  257. }

  258. }

  259. });

  260. }

  261.  
  262. /**

  263. * Convert byte[] to hex string.这里我们可以将byte转换成int,然后利用Integer.toHexString(int)来转换成16进制字符串。

  264. * @param src byte[] data

  265. * @return hex string

  266. */

  267. public static String bytesToHexString(byte[] src){

  268. StringBuilder stringBuilder = new StringBuilder("");

  269. if (src == null || src.length <= 0) {

  270. return null;

  271. }

  272. for (int i = 0; i < src.length; i++) {

  273. int v = src[i] & 0xFF;

  274. String hv = Integer.toHexString(v);

  275. if (hv.length() < 2) {

  276. stringBuilder.append(0);

  277. }

  278. stringBuilder.append(hv);

  279. }

  280. return stringBuilder.toString();

  281. }

  282.  
  283. /**

  284. * Convert hex string to byte[]

  285. * @param hexString the hex string

  286. * @return byte[]

  287. */

  288. public static byte[] hexStringToBytes(String hexString) {

  289. if (hexString == null || hexString.equals("")) {

  290. return null;

  291. }

  292. hexString = hexString.toUpperCase();

  293. int length = hexString.length() / 2;

  294. char[] hexChars = hexString.toCharArray();

  295. byte[] d = new byte[length];

  296. for (int i = 0; i < length; i++) {

  297. int pos = i * 2;

  298. d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));

  299. }

  300. return d;

  301. }

  302. /**

  303. * Convert char to byte

  304. * @param c char

  305. * @return byte

  306. */

  307. private static byte charToByte(char c) {

  308. return (byte) "0123456789ABCDEF".indexOf(c);

  309. }

  310.  
  311. }

代码可能暂时比较乱,我就挑主要的给大家梳理下逻辑,注释中有的已经写的很清楚了。

(1)先在界面上Toolbar上添加扫描的按钮,点击扫描按钮会跳转到扫描界面。

贴出来main.xml的菜单文件

 
  1. <menu xmlns:android="http://schemas.android.com/apk/res/android"

  2. xmlns:app="http://schemas.android.com/apk/res-auto"

  3. xmlns:tools="http://schemas.android.com/tools"

  4. tools:context="com.cjt.bluetoothscm.MainActivity">

  5. <item

  6. android:id="@+id/action_scan"

  7. android:orderInCategory="100"

  8. android:icon="@drawable/ic_scan"

  9. android:title="@string/action_settings"

  10. tools:ignore="AppCompatResource"

  11. app:showAsAction="always" />

  12. </menu>

其中有个icon,引用的是drawable下的一个VectorDrawable资源文件,ic_scan.xml

 
  1. <vector android:height="42dp" android:viewportHeight="1024"

  2. android:viewportWidth="1024" android:width="42dp" xmlns:android="http://schemas.android.com/apk/res/android">

  3. <path android:fillColor="#ffffff" android:pathData="M512,960c-249.6,0 -448,-198.4 -448,-448s198.4,-448 448,-448 448,198.4 448,448 -198.4,448 -448,448zM512,883.2c204.8,0 371.2,-166.4 371.2,-371.2S716.8,140.8 512,140.8 140.8,307.2 140.8,512s166.4,371.2 371.2,371.2z"/>

  4. <path android:fillColor="#3cbc74" android:pathData="M512,787.2c-153.6,0 -275.2,-121.6 -275.2,-275.2S358.4,236.8 512,236.8s275.2,121.6 275.2,275.2 -121.6,275.2 -275.2,275.2zM512,748.8c128,0 236.8,-108.8 236.8,-236.8S640,275.2 512,275.2 275.2,384 275.2,512 384,748.8 512,748.8z"/>

  5. <path android:fillColor="#ffffff" android:pathData="M512,512m-38.4,0a38.4,38.4 0,1 0,76.8 0,38.4 38.4,0 1,0 -76.8,0Z"/>

  6. <path android:fillColor="#ffffff" android:pathData="M524.8,531.2l345.6,-217.6 -19.2,-32 -352,211.2zM492.8,25.6L492.8,320c0,12.8 6.4,19.2 19.2,19.2s19.2,-6.4 19.2,-19.2L531.2,25.6c0,-12.8 -6.4,-19.2 -19.2,-19.2s-19.2,6.4 -19.2,19.2zM998.4,492.8L704,492.8c-12.8,0 -19.2,6.4 -19.2,19.2s6.4,19.2 19.2,19.2h294.4c12.8,0 19.2,-6.4 19.2,-19.2s-6.4,-19.2 -19.2,-19.2zM531.2,998.4L531.2,704c0,-12.8 -6.4,-19.2 -19.2,-19.2s-19.2,6.4 -19.2,19.2v294.4c0,12.8 6.4,19.2 19.2,19.2s19.2,-6.4 19.2,-19.2zM25.6,531.2L320,531.2c12.8,0 19.2,-6.4 19.2,-19.2s-6.4,-19.2 -19.2,-19.2L25.6,492.8c-12.8,0 -19.2,6.4 -19.2,19.2s6.4,19.2 19.2,19.2z"/>

  7. </vector>

使用这个的好处就是,改变大小的时候不会失真,因为使用的矢量作图。 

(2)蓝牙连接的处理,这一块大家要恶补下关于蓝牙UUID的知识,由于我也研究的不深入,所以就不误导大家了,梳理下onActivityResult回调方法

(3)我把client写到了全局Application中,如下

 
  1. package com.cjt.bluetoothscm;

  2.  
  3. import android.app.Application;

  4.  
  5. import com.inuker.bluetooth.library.BluetoothClient;

  6.  
  7. /*****************

  8. * 包名:com.cjt.bluetoothscm

  9. * 类名:MyApp.java

  10. * 时间:2018/9/17 23:46

  11. * 作者:Cao Jiangtao

  12. * 首页:https://1989jiangtao.github.io/index.html

  13. ******************/

  14.  
  15. public class MyApp extends Application {

  16.  
  17. private static MyApp instance ;

  18.  
  19. private static BluetoothClient bluetoothClient ;

  20.  
  21. public static MyApp getInstance() {

  22. return instance;

  23. }

  24.  
  25. public static BluetoothClient getBluetoothClient() {

  26. return bluetoothClient;

  27. }

  28.  
  29. @Override

  30. public void onCreate() {

  31. super.onCreate();

  32. instance = this ;

  33.  
  34. // 新建全局的蓝牙客户端实例

  35. bluetoothClient = new BluetoothClient(this);

  36.  
  37. }

  38.  
  39. }

 (4)看下扫描界面的界面布局文件和java代码

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="match_parent"

  7. tools:context=".ScanResultActivity">

  8.  
  9.  
  10. <android.support.v7.widget.RecyclerView

  11. android:id="@+id/result_recycle_list"

  12. android:layout_width="match_parent"

  13. android:layout_height="0dp"

  14. android:layout_marginBottom="8dp"

  15. android:layout_marginTop="8dp"

  16. android:paddingLeft="8dp"

  17. android:paddingRight="8dp"

  18. app:layout_constraintBottom_toTopOf="@+id/btn_exit"

  19. app:layout_constraintEnd_toEndOf="parent"

  20. app:layout_constraintStart_toStartOf="parent"

  21. app:layout_constraintTop_toTopOf="parent" />

  22.  
  23. <ProgressBar

  24. android:id="@+id/progressBar"

  25. style="?android:attr/progressBarStyle"

  26. android:layout_width="wrap_content"

  27. android:layout_height="wrap_content"

  28. android:layout_marginBottom="8dp"

  29. android:layout_marginEnd="8dp"

  30. android:layout_marginLeft="8dp"

  31. android:layout_marginRight="8dp"

  32. android:layout_marginStart="8dp"

  33. android:layout_marginTop="8dp"

  34. android:visibility="gone"

  35. app:layout_constraintBottom_toBottomOf="parent"

  36. app:layout_constraintEnd_toEndOf="parent"

  37. app:layout_constraintStart_toStartOf="parent"

  38. app:layout_constraintTop_toTopOf="parent" />

  39.  
  40. <Button

  41. android:id="@+id/btn_exit"

  42. android:layout_width="0dp"

  43. android:layout_height="wrap_content"

  44. android:layout_marginBottom="8dp"

  45. android:layout_marginEnd="16dp"

  46. android:layout_marginLeft="16dp"

  47. android:layout_marginRight="16dp"

  48. android:layout_marginStart="16dp"

  49. android:textSize="20sp"

  50. android:textStyle="bold"

  51. android:background="@color/colorRed"

  52. android:textColor="@color/colorWhite"

  53. android:text="@string/btn_exit"

  54. app:layout_constraintBottom_toBottomOf="parent"

  55. app:layout_constraintEnd_toEndOf="parent"

  56. app:layout_constraintStart_toStartOf="parent" />

  57.  
  58. </android.support.constraint.ConstraintLayout>

 
  1. package com.cjt.bluetoothscm;

  2.  
  3. import android.bluetooth.BluetoothDevice;

  4. import android.support.v7.app.AppCompatActivity;

  5. import android.os.Bundle;

  6. import android.support.v7.widget.LinearLayoutManager;

  7. import android.support.v7.widget.RecyclerView;

  8. import android.text.TextUtils;

  9. import android.util.Log;

  10. import android.view.View;

  11. import android.widget.Button;

  12. import android.widget.ProgressBar;

  13.  
  14. import com.inuker.bluetooth.library.search.SearchRequest;

  15. import com.inuker.bluetooth.library.search.SearchResult;

  16. import com.inuker.bluetooth.library.search.response.SearchResponse;

  17.  
  18. import java.util.ArrayList;

  19. import java.util.HashSet;

  20. import java.util.List;

  21. /*****************

  22. * 包名:com.cjt.bluetoothscm

  23. * 类名:ScanResultActivity.java

  24. * 时间:2018/9/17 23:49

  25. * 作者:Cao Jiangtao

  26. * 首页:https://1989jiangtao.github.io/index.html

  27. ******************/

  28. public class ScanResultActivity extends AppCompatActivity implements View.OnClickListener {

  29.  
  30. RecyclerView recyclerView ; // 列表展示扫描的结果

  31.  
  32. ProgressBar progressBar ; // 页面上的进度条

  33.  
  34. Button exitBtn ; // 退出按钮

  35.  
  36. RecycleAdapter adapter ; // 列表适配器

  37.  
  38. List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>();

  39.  
  40. @Override

  41. protected void onCreate(Bundle savedInstanceState) {

  42. super.onCreate(savedInstanceState);

  43. setContentView(R.layout.activity_scan_result);

  44.  
  45. initView(); // 界面初始化

  46.  
  47. startScan(); // 开启扫描

  48.  
  49. }

  50.  
  51. @Override

  52. protected void onDestroy() {

  53. // 停止扫描

  54. MyApp.getBluetoothClient().stopSearch();

  55. super.onDestroy();

  56. }

  57.  
  58. /**开启蓝牙扫描**/

  59. private void startScan() {

  60. // 新建一个扫描结果集

  61. SearchRequest request = new SearchRequest.Builder()

  62. .searchBluetoothLeDevice(3000, 3) // 先扫BLE设备3次,每次3s

  63. .searchBluetoothClassicDevice(5000) // 再扫经典蓝牙5s

  64. .searchBluetoothLeDevice(2000) // 再扫BLE设备2s

  65. .build();

  66.  
  67. // 开始扫描

  68. MyApp.getBluetoothClient().search(request, new SearchResponse() {

  69. @Override

  70. public void onSearchStarted() {

  71. progressBar.setVisibility(View.VISIBLE);

  72. Log.d("CJT" , "onSearchStarted ***** ");

  73. }

  74.  
  75. @Override

  76. public void onDeviceFounded(SearchResult device) {

  77. Log.d("CJT" , "onDeviceFounded == name :"+ device.device.getName()+", address : " + device.device.getAddress());

  78. if(!TextUtils.isEmpty(device.device.getName())){

  79. bluetoothDeviceList.add(device.device);

  80. }

  81. }

  82.  
  83. @Override

  84. public void onSearchStopped() {

  85. progressBar.setVisibility(View.GONE);

  86. // 实时更新列表适配器

  87. adapter.notifyData(removeDuplicate(bluetoothDeviceList));

  88. Log.d("CJT" , "onSearchStopped ###### 设备数量 :"+removeDuplicate(bluetoothDeviceList).size());

  89. }

  90.  
  91. @Override

  92. public void onSearchCanceled() {

  93. progressBar.setVisibility(View.GONE);

  94. }

  95. });

  96.  
  97. }

  98.  
  99. /***

  100. * 列表去重复

  101. * @param list 待去除重复数据的列表

  102. * @return 返回去重后的列表

  103. */

  104. public static List removeDuplicate(List list) {

  105. HashSet h = new HashSet(list);

  106. list.clear();

  107. list.addAll(h);

  108. return list;

  109. }

  110.  
  111. /**初始化页面和控件**/

  112. private void initView(){

  113. // 初始化页面控件

  114. recyclerView = findViewById(R.id.result_recycle_list);

  115. progressBar = findViewById(R.id.progressBar);

  116. exitBtn = findViewById(R.id.btn_exit);

  117.  
  118. exitBtn.setOnClickListener(this);

  119.  
  120. // 设置recycleView显示方式

  121. recyclerView.setLayoutManager(new LinearLayoutManager(this , LinearLayoutManager.VERTICAL , false));

  122.  
  123. adapter = new RecycleAdapter();

  124. // 设置适配器

  125. recyclerView.setAdapter(adapter);

  126.  
  127. }

  128.  
  129. @Override

  130. public void onClick(View v) {

  131. finish();

  132. }

  133. }

代码中有一个去重复的方法,避免最后的列表中很多重复的设备,因为蓝牙在后台不停的扫描,会有很多重复的设备。

(5)看下列表适配器的写法,我用了RecycleView来列表展示已扫描到的设备,同时添加了点击事件,当选择和点击的时候就携带相关的设备信息跳转到MainActivity,进行读写操作。

 
  1. package com.cjt.bluetoothscm;

  2.  
  3. import android.app.Activity;

  4. import android.bluetooth.BluetoothDevice;

  5. import android.content.Intent;

  6. import android.support.annotation.NonNull;

  7. import android.support.constraint.ConstraintLayout;

  8. import android.support.v7.widget.RecyclerView;

  9. import android.util.Log;

  10. import android.view.LayoutInflater;

  11. import android.view.View;

  12. import android.view.ViewGroup;

  13. import android.widget.TextView;

  14.  
  15. import java.util.ArrayList;

  16. import java.util.List;

  17.  
  18. /*****************

  19. * 包名:com.cjt.bluetoothscm

  20. * 类名:RecycleAdapter.java

  21. * 时间:2018/9/14 10:14

  22. * 作者:Cao Jiangtao

  23. * 首页:https://1989jiangtao.github.io/index.html

  24. ******************/

  25. public class RecycleAdapter extends RecyclerView.Adapter<RecycleAdapter.MyViewHolder> {

  26.  
  27. public static final String EXTRA_DEVICE = "extra_device";

  28.  
  29. List<BluetoothDevice> deviceList = new ArrayList<>();

  30.  
  31. @NonNull

  32. @Override

  33. public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

  34. Log.d("CJT"," RecycleAdapter --- onCreateViewHolder -- ");

  35. return new RecycleAdapter.MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_device_layout,parent,false));

  36. }

  37.  
  38. @Override

  39. public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {

  40. holder.deviceName.setText(getItem(position).getName());

  41. holder.deviceAddress.setText(getItem(position).getAddress());

  42. // 为列表添加点击事件,传入的参数是当前项的蓝牙设备BluetoothDevice

  43. holder.deviceItem.setOnClickListener(new itemClick(getItem(position)));

  44. }

  45.  
  46. @Override

  47. public int getItemCount() {

  48. return deviceList.size();

  49. }

  50.  
  51. /***

  52. * 获取指定位置的元素

  53. * @param position

  54. * @return

  55. */

  56. private BluetoothDevice getItem(int position){

  57. return deviceList.get(position);

  58. }

  59.  
  60. /***

  61. * 数据集合改变的方法

  62. * @param deviceList

  63. */

  64. public void notifyData(List<BluetoothDevice> deviceList){

  65. Log.d("CJT" , "notifyData == %%%%%%%%%%%%%% ");

  66. this.deviceList = deviceList ;

  67. this.notifyDataSetChanged();

  68. }

  69.  
  70.  
  71. class MyViewHolder extends RecyclerView.ViewHolder{

  72.  
  73. ConstraintLayout deviceItem ; // item的单个布局

  74. TextView deviceName ; // 设备名称

  75. TextView deviceAddress; // 设备地址

  76.  
  77. public MyViewHolder(View itemView) {

  78. super(itemView);

  79. deviceItem = itemView.findViewById(R.id.item_layout);

  80. deviceName = itemView.findViewById(R.id.item_device_name);

  81. deviceAddress = itemView.findViewById(R.id.item_device_address);

  82. }

  83. }

  84.  
  85. /**点击事件内部类**/

  86. private class itemClick implements View.OnClickListener {

  87.  
  88. private BluetoothDevice device ;

  89.  
  90. public itemClick(BluetoothDevice item) {

  91. this.device = item ;

  92. }

  93.  
  94. @Override

  95. public void onClick(View v) {

  96. // 设置返回数据

  97. Intent intent = new Intent();

  98. intent.putExtra(EXTRA_DEVICE, device);

  99. Log.d("CJT" , "device.getAddress() == "+device.getAddress());

  100. // 设置返回值并结束程序

  101. ((Activity)v.getContext()).setResult(Activity.RESULT_OK, intent);

  102. ((Activity)v.getContext()).finish();

  103. }

  104. }

  105. }

 (6)还有一个表盘布局的文件,关于自定义View,有不明白的同学可以自行去学习下,我这里就不深入讲解了。

4.小结。

     主要的代码就是上面给大家展示的部分,由于使用的第三方的蓝牙库,这个蓝牙库呢还有一个比较有缺陷的地方就是可以扫描到经典蓝牙的设备,却没有相应的connect方法,更不能去读写了,所以后面我会再去完善连接经典蓝牙模块的部分,本次蓝牙模块我使用的是蓝牙BLE模块cc2541。

     好了,本篇就到此为止了,后面代码上传了GitHub我会跟进更新地址,另外下一篇我会主要讲解和演示单片机接收数据的部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值