本文使用的是flutter_blue_plus
插件来实现链接蓝牙之后,和设备直接实现数据互相传输的功能。
iOS权限设置
<key>NSBluetoothAlwaysUsageDescription</key>
<string>App需要您的同意,才能访问蓝牙,进行设备连接,数据通讯服务</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>App需要您的同意,才能访问蓝牙,进行设备连接,数据通讯服务</string>
Android权限设置
<!-- 蓝牙-->
<!-- google play store需要-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
<!-- Android 12-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11 及以下-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION " />
flutter_blue_plus
插件flutter_blue_plus: ^1.31.8
代码如下:
import 'dart:async';
import 'dart:io';
import 'package:demo/view/device_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';
class BluetoothPage extends StatefulWidget {
const BluetoothPage({super.key});
@override
State<BluetoothPage> createState() => _BluetoothPageState();
}
class _BluetoothPageState extends State<BluetoothPage> {
///当前已经连接的蓝牙设备
List<BluetoothDevice> _systemDevices = [];
///扫描到的蓝牙设备
List<ScanResult> _scanResults = [];
late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
late StreamSubscription<bool> _isScanningSubscription;
@override
void initState() {
super.initState();
_scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) {
_scanResults = results;
if (mounted) {
setState(() {});
}
}, onError: (error) {
print('Scan Error:$error');
});
_isScanningSubscription = FlutterBluePlus.isScanning.listen((state) {
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_scanResultsSubscription.cancel();
_isScanningSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("蓝牙"),
),
body: ListView(
children: [
..._buildSystemDeviceTiles(),
..._buildScanResultTiles(),
],
),
floatingActionButton: FlutterBluePlus.isScanningNow
? FloatingActionButton(
onPressed: () {
FlutterBluePlus.stopScan();
},
backgroundColor: Colors.red,
child: const Text("Stop"),
)
: FloatingActionButton(
onPressed: () async {
try {
_systemDevices = await FlutterBluePlus.systemDevices;
print('dc-----_systemDevices$_systemDevices');
} catch (e) {
print("Stop Scan Error:$e");
}
try {
// android is slow when asking for all advertisements,
// so instead we only ask for 1/8 of them
int divisor = Platform.isAndroid ? 8 : 1;
await FlutterBluePlus.startScan(
timeout: const Duration(seconds: 15),
continuousUpdates: true,
continuousDivisor: divisor);
} catch (e) {
print("Stop Scan Error:$e");
}
if (mounted) {
setState(() {});
}
},
child: const Text("SCAN"),
),
);
}
List<Widget> _buildSystemDeviceTiles() {
return _systemDevices.map((device) {
return ListTile(
title: Text(device.platformName),
subtitle: Text(device.remoteId.toString()),
trailing: ElevatedButton(
onPressed: () {
Get.to(DeviceScreen(device: device));
},
child: const Text('CONNECT'),
),
);
}).toList();
}
List<Widget> _buildScanResultTiles() {
return _scanResults
.map(
(scanResult) => ListTile(
title: Text(
scanResult.device.platformName,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
scanResult.device.remoteId.toString(),
),
trailing: ElevatedButton(
onPressed: () {
Get.to(DeviceScreen(device: scanResult.device));
},
child: const Text('CONNECT'),
),
),
)
.toList();
}
}
代码如下
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/route_manager.dart';
class DeviceScreen extends StatefulWidget {
final BluetoothDevice device;
const DeviceScreen({
super.key,
required this.device,
});
@override
State<DeviceScreen> createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
List<BluetoothService> _services = [];
BluetoothConnectionState _connectionState =
BluetoothConnectionState.disconnected;
late StreamSubscription<BluetoothConnectionState>
_connectionStateSubscription;
bool get isConnected {
return _connectionState == BluetoothConnectionState.connected;
}
@override
void initState() {
super.initState();
_connectionStateSubscription =
widget.device.connectionState.listen((state) async {
_connectionState = state;
if (state == BluetoothConnectionState.connected) {
_services = []; // must rediscover services
}
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_connectionStateSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.device.platformName,
),
actions: [
TextButton(
onPressed: isConnected
? () async {
await widget.device.disconnect();
}
: () async {
await widget.device.connect();
print("Connect: Success");
},
child: Text(
isConnected ? "DISCONNECT" : "CONNECT",
),
),
],
),
body: Column(
children: [
ListTile(
title: Text(
'Device is ${_connectionState.toString().split('.')[1]}.',
),
trailing: TextButton(
onPressed: () async {
if (!isConnected) {
Get.snackbar('title', '请先连接蓝牙设备');
return;
}
_services = await widget.device.discoverServices();
setState(() {});
},
child: const Text("Get Services"),
),
),
..._buildServiceTiles(),
],
),
);
}
List<Widget> _buildServiceTiles() {
return _services.map(
(service) {
return ExpansionTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Service', style: TextStyle(color: Colors.blue)),
Text(
'0x${service.uuid.str.toUpperCase()}',
),
],
),
children: service.characteristics.map((c) {
return CharacteristicTile(
characteristic: c,
descriptorTiles: c.descriptors
.map((d) => DescriptorTile(descriptor: d))
.toList(),
);
}).toList(),
);
},
).toList();
}
}
代码如下:
class CharacteristicTile extends StatefulWidget {
final BluetoothCharacteristic characteristic;
final List<DescriptorTile> descriptorTiles;
const CharacteristicTile(
{Key? key, required this.characteristic, required this.descriptorTiles})
: super(key: key);
@override
State<CharacteristicTile> createState() => _CharacteristicTileState();
}
class _CharacteristicTileState extends State<CharacteristicTile> {
List<int> _value = [];
late StreamSubscription<List<int>> _lastValueSubscription;
@override
void initState() {
super.initState();
_lastValueSubscription =
widget.characteristic.lastValueStream.listen((value) {
_value = value;
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_lastValueSubscription.cancel();
super.dispose();
}
BluetoothCharacteristic get c => widget.characteristic;
List<int> _getRandomBytes() {
// 将字符串转换为字节数组
String data = 'Hello, Bluetooth!';
List<int> bytes = utf8.encode(data);
return bytes;
}
Future onReadPressed() async {
try {
await c.read();
print("Read: Success");
} catch (e) {
print("Read Error:");
}
}
Future onWritePressed() async {
try {
await c.write(_getRandomBytes(),
withoutResponse: c.properties.writeWithoutResponse);
print("Write: Success");
if (c.properties.read) {
await c.read();
}
} catch (e) {
print("Write Error:");
}
}
Future onSubscribePressed() async {
try {
String op = c.isNotifying == false ? "Subscribe" : "Unubscribe";
await c.setNotifyValue(c.isNotifying == false);
print("$op : Success");
if (c.properties.read) {
await c.read();
}
if (mounted) {
setState(() {});
}
} catch (e) {
print("Subscribe Error:");
}
}
Widget buildUuid(BuildContext context) {
String uuid = '0x${widget.characteristic.uuid.str.toUpperCase()}';
return Text(uuid);
}
Widget buildValue(BuildContext context) {
String data = _value.toString();
return Text(
data,
style: const TextStyle(fontSize: 13),
);
}
Widget buildReadButton(BuildContext context) {
return TextButton(
child: const Text(
"Read",
style: TextStyle(fontSize: 13),
),
onPressed: () async {
await onReadPressed();
if (mounted) {
setState(() {});
}
});
}
Widget buildWriteButton(BuildContext context) {
bool withoutResp = widget.characteristic.properties.writeWithoutResponse;
return TextButton(
child: Text(withoutResp ? "WriteNoResp" : "Write",
style: const TextStyle(fontSize: 13, color: Colors.grey)),
onPressed: () async {
await onWritePressed();
if (mounted) {
setState(() {});
}
});
}
Widget buildSubscribeButton(BuildContext context) {
bool isNotifying = widget.characteristic.isNotifying;
return TextButton(
child: Text(isNotifying ? "Unsubscribe" : "Subscribe"),
onPressed: () async {
await onSubscribePressed();
if (mounted) {
setState(() {});
}
});
}
Widget buildButtonRow(BuildContext context) {
bool read = widget.characteristic.properties.read;
bool write = widget.characteristic.properties.write;
bool notify = widget.characteristic.properties.notify;
bool indicate = widget.characteristic.properties.indicate;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (read) buildReadButton(context),
if (write) buildWriteButton(context),
if (notify || indicate) buildSubscribeButton(context),
],
);
}
@override
Widget build(BuildContext context) {
return ExpansionTile(
title: ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text(
'Characteristic',
style: TextStyle(fontSize: 13),
),
buildUuid(context),
buildValue(context),
],
),
subtitle: buildButtonRow(context),
contentPadding: const EdgeInsets.all(0.0),
),
children: widget.descriptorTiles,
);
}
}
class DescriptorTile extends StatefulWidget {
final BluetoothDescriptor descriptor;
const DescriptorTile({Key? key, required this.descriptor}) : super(key: key);
@override
State<DescriptorTile> createState() => _DescriptorTileState();
}
class _DescriptorTileState extends State<DescriptorTile> {
List<int> _value = [];
late StreamSubscription<List<int>> _lastValueSubscription;
@override
void initState() {
super.initState();
_lastValueSubscription = widget.descriptor.lastValueStream.listen((value) {
_value = value;
if (mounted) {
setState(() {});
}
});
}
@override
void dispose() {
_lastValueSubscription.cancel();
super.dispose();
}
BluetoothDescriptor get d => widget.descriptor;
List<int> _getRandomBytes() {
// 将字符串转换为字节数组
String data = 'Hello, Bluetooth!';
return utf8.encode(data);
}
Future onReadPressed() async {
try {
await d.read();
print("Descriptor Read : Success");
} catch (e) {
print("Descriptor Read Error:");
}
}
Future onWritePressed() async {
try {
await d.write(_getRandomBytes());
print("Descriptor Write : Success");
} catch (e) {
print("Descriptor Write Error:");
}
}
Widget buildUuid(BuildContext context) {
String uuid = '0x${widget.descriptor.uuid.str.toUpperCase()}';
return Text(
uuid,
style: TextStyle(fontSize: 13),
);
}
Widget buildValue(BuildContext context) {
String data = _value.toString();
return Text(
data,
style: TextStyle(fontSize: 13),
);
}
Widget buildReadButton(BuildContext context) {
return TextButton(
child: Text("Read"),
onPressed: onReadPressed,
);
}
Widget buildWriteButton(BuildContext context) {
return TextButton(
child: Text("Write"),
onPressed: onWritePressed,
);
}
Widget buildButtonRow(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
buildReadButton(context),
buildWriteButton(context),
],
);
}
@override
Widget build(BuildContext context) {
return ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text(
'Descriptor',
style: TextStyle(fontSize: 13),
),
buildUuid(context),
buildValue(context),
],
),
subtitle: buildButtonRow(context),
);
}
}
发送数据的代码:
Future onWritePressed() async {
try {
await c.write(_getRandomBytes(),
withoutResponse: c.properties.writeWithoutResponse);
print("Write: Success");
if (c.properties.read) {
await c.read();
}
} catch (e) {
print("Write Error:");
}
}
List<int> _getRandomBytes() {
// 将字符串转换为字节数组
String data = 'Hello, Bluetooth!';
List<int> bytes = utf8.encode(data);
return bytes;
}
flutter_blue_plus
给出的官方demo代码