今日的程序码 => GITHUB
讲解官方范例的权限、扫描、广播的部分。官方范例是使用 GetX 来写。
这边,我有稍微改了一下一些参数的名称,所以会和官方范例有些微不同。
这边是手动去检查权限,还要再去记住监听器使否开启。
class RequirementStateController extends GetxController {
/// 蓝芽的状态
var bluetoothState = BluetoothState.stateOff.obs;
/// 蓝芽授权状态
var authorizationStatus = AuthorizationStatus.notDetermined.obs;
/// 定位开启状态
var locationService = false.obs;
/// 是否开始 broadcasting
var _startBroadcasting = false.obs;
/// 是否开始扫描
var _startScanning = false.obs;
/// 是否暂停扫描
var _pauseScanning = false.obs;
/// 蓝夜是否开启
bool get bluetoothEnabled => bluetoothState.value == BluetoothState.stateOn;
/// 蓝夜是否开启
bool get authorizationStatusOk =>
authorizationStatus.value == AuthorizationStatus.allowed ||
authorizationStatus.value == AuthorizationStatus.always;
/// 是否有开启定位
bool get locationServiceEnabled => locationService.value;
/// 更新蓝芽的状态
updateBluetoothState(BluetoothState state) {
bluetoothState.value = state;
}
/// 更新蓝芽的登入状态
updateAuthorizationStatus(AuthorizationStatus status) {
authorizationStatus.value = status;
}
/// 更新 Location 的状态
updateLocationService(bool flag) {
locationService.value = flag;
}
/// 更新蓝芽的登入状态
startBroadcasting() {
_startBroadcasting.value = true;
}
/// 停止 Broadcasting
stopBroadcasting() {
_startBroadcasting.value = false;
}
/// 停止 Scanning
startScanning() {
_startScanning.value = true;
_pauseScanning.value = false;
}
/// 暂停 Scanning
pauseScanning() {
_startScanning.value = false;
_pauseScanning.value = true;
}
/// get开始 BroadCastStream
Stream<bool> get startBroadcastStream {
return _startBroadcasting.stream;
}
/// get StartScanningStream
Stream<bool> get startScanningStream {
return _startScanning.stream;
}
/// pause scanningString
Stream<bool> get pauseScanningStream {
return _pauseScanning.stream;
}
}
resumed 可见、可操作(进入前景)
inactive可见、不可遭做 ( 如果来了个电话,电话会进入前景,因此会触发此状态, the application is visible and responding to user input)
paused 不可见、不可操作(进入背景)
detached 虽然还在运行,但已经没有任何存在的页面
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
final controller = Get.find<RequirementStateController>();
/// 监听 BluetoothState 的状态。
StreamSubscription<BluetoothState>? _streamBluetooth;
int currentIndex = 0;
@override
void initState() {
// 新增观察者
WidgetsBinding.instance?.addObserver(this);
super.initState();
listeningState();
}
// 监听蓝芽状态。
listeningState() async {
/// 下面这一行,是我自己加上去的, initState 的时候,先去检查权限。
await checkAllRequirements();
/// 监听状态,状态改变检查权限
_streamBluetooth = flutterBeacon
.bluetoothStateChanged()
.listen((BluetoothState state) async {
controller.updateBluetoothState(state);
await checkAllRequirements();
});
}
/// 检查权限
checkAllRequirements() async {
final bluetoothState = await flutterBeacon.bluetoothState;
controller.updateBluetoothState(bluetoothState);
print('BLUETOOTH $bluetoothState');
final authorizationStatus = await flutterBeacon.authorizationStatus;
controller.updateAuthorizationStatus(authorizationStatus);
print('AUTHORIZATION $authorizationStatus');
final locationServiceEnabled =
await flutterBeacon.checkLocationServicesIfEnabled;
controller.updateLocationService(locationServiceEnabled);
print('LOCATION SERVICE $locationServiceEnabled');
if (controller.bluetoothEnabled &&
controller.authorizationStatusOk &&
controller.locationServiceEnabled) {
print('STATE READY');
if (currentIndex == 0) {
print('SCANNING');
controller.startScanning();
} else {
print('BROADCASTING');
controller.startBroadcasting();
}
} else {
print('STATE NOT READY');
controller.pauseScanning();
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
print('AppLifecycleState = $state');
if (state == AppLifecycleState.inactive) {
// 如果来了个电话,电话会进入前景,因此会触发此状态。
}
if (state == AppLifecycleState.resumed) {
// 应用进入前景
if (_streamBluetooth != null) {
if (_streamBluetooth!.isPaused) {
_streamBluetooth?.resume();
}
}
await checkAllRequirements();
} else if (state == AppLifecycleState.paused) {
// 应用进入背景
_streamBluetooth?.pause();
}
}
@override
void dispose() {
_streamBluetooth?.cancel();
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Beacon'),
centerTitle: false,
actions: <Widget>[
Obx(() {
if (!controller.locationServiceEnabled)
return IconButton(
tooltip: 'Not Determined',
icon: Icon(Icons.portable_wifi_off),
color: Colors.grey,
onPressed: () {},
);
if (!controller.authorizationStatusOk)
return IconButton(
tooltip: 'Not Authorized',
icon: Icon(Icons.portable_wifi_off),
color: Colors.red,
onPressed: () async {
await flutterBeacon.requestAuthorization;
},
);
return IconButton(
tooltip: 'Authorized',
icon: Icon(Icons.wifi_tethering),
color: Colors.blue,
onPressed: () async {
await flutterBeacon.requestAuthorization;
},
);
}),
Obx(() {
return IconButton(
tooltip: controller.locationServiceEnabled
? 'Location Service ON'
: 'Location Service OFF',
icon: Icon(
controller.locationServiceEnabled
? Icons.location_on
: Icons.location_off,
),
color:
controller.locationServiceEnabled ? Colors.blue : Colors.red,
onPressed: controller.locationServiceEnabled
? () {}
: handleOpenLocationSettings,
);
}),
Obx(() {
final state = controller.bluetoothState.value;
if (state == BluetoothState.stateOn) {
return IconButton(
tooltip: 'Bluetooth ON',
icon: Icon(Icons.bluetooth_connected),
onPressed: () {},
color: Colors.lightBlueAccent,
);
}
if (state == BluetoothState.stateOff) {
return IconButton(
tooltip: 'Bluetooth OFF',
icon: Icon(Icons.bluetooth),
onPressed: handleOpenBluetooth,
color: Colors.red,
);
}
return IconButton(
icon: Icon(Icons.bluetooth_disabled),
tooltip: 'Bluetooth State Unknown',
onPressed: () {},
color: Colors.grey,
);
}),
],
),
body: IndexedStack(
index: currentIndex,
children: [
// TabScanning(),
TabScanning(),
TabBroadcasting(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
if (currentIndex == 0) {
controller.startScanning();
} else {
controller.pauseScanning();
controller.startBroadcasting();
}
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.bluetooth_audio),
label: 'Broadcast',
),
],
),
);
}
/// 开启定位
handleOpenLocationSettings() async {
if (Platform.isAndroid) {
await flutterBeacon.openLocationSettings;
} else if (Platform.isIOS) {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Location Services Off'),
content: Text(
'Please enable Location Services on Settings > Privacy > Location Services.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
);
},
);
}
}
/// 开启蓝芽
handleOpenBluetooth() async {
if (Platform.isAndroid) {
try {
await flutterBeacon.openBluetoothSettings;
} on PlatformException catch (e) {
print(e);
}
} else if (Platform.isIOS) {
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Bluetooth is Off'),
content: Text('Please enable Bluetooth on Settings > Bluetooth.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
);
},
);
}
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_beacon/flutter_beacon.dart';
import 'package:flutter_beacon_example/controller/requirement_state_controller.dart';
import 'package:get/get.dart';
class TabScanning extends StatefulWidget {
@override
_TabScanningState createState() => _TabScanningState();
}
class _TabScanningState extends State<TabScanning> {
/// 监听 RangingResult 的资料,用来管理是否有在监听。
StreamSubscription<RangingResult>? _streamRanging;
/// 用来记录 beacons 的资料。
final _regionBeacons = <Region, List<Beacon>>{};
/// 把 _regionBeacons 的 mpa value 全部存入这个 list
final _beacons = <Beacon>[];
final controller = Get.find<RequirementStateController>();
@override
void initState() {
super.initState();
/// 监听开启扫描的 bool stream
controller.startScanningStream.listen((flag) {
if (flag == true) {
initScanBeacon();
}
});
/// 监听开启扫描的 bool stream
controller.pauseScanningStream.listen((flag) {
if (flag == true) {
pauseScanBeacon();
}
});
}
/// 开始扫描。
initScanBeacon() async {
/// 初始化 Scanning
await flutterBeacon.initializeScanning;
/// 没权限的话,就停止开始扫描
if (!controller.authorizationStatusOk ||
!controller.locationServiceEnabled ||
!controller.bluetoothEnabled) {
print(
'RETURNED, authorizationStatusOk=${controller
.authorizationStatusOk}, '
'locationServiceEnabled=${controller.locationServiceEnabled}, '
'bluetoothEnabled=${controller.bluetoothEnabled}');
return;
}
/// 定义要扫描的地区。
final regions = <Region>[
Region(
identifier: 'Cubeacon',
proximityUUID: 'CB10023F-A318-3394-4199-A8730C7C1AEC',
),
Region(
identifier: 'BeaconType2',
proximityUUID: '6a84c716-0f2a-1ce9-f210-6a63bd873dd9',
),
];
/// 如果他监听器被暂停了,就恢复它。
if (_streamRanging != null) {
if (_streamRanging!.isPaused) {
_streamRanging?.resume();
return;
}
}
/// 监听器开始监听,并把资料存入变数里面。
_streamRanging =
flutterBeacon.ranging(regions).listen((RangingResult result) {
print(result);
if (mounted) {
setState(() {
_regionBeacons[result.region] = result.beacons;
_beacons.clear();
_regionBeacons.values.forEach((list) {
_beacons.addAll(list);
});
_beacons.sort(_compareParameters);
});
}
});
}
/// 暂停监听器、并清空资料。
pauseScanBeacon() async {
_streamRanging?.pause();
if (_beacons.isNotEmpty) {
setState(() {
_beacons.clear();
});
}
}
/// Beacon 的排序
int _compareParameters(Beacon a, Beacon b) {
int compare = a.proximityUUID.compareTo(b.proximityUUID);
if (compare == 0) {
compare = a.major.compareTo(b.major);
}
if (compare == 0) {
compare = a.minor.compareTo(b.minor);
}
return compare;
}
/// 关病监听器。
@override
void dispose() {
_streamRanging?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _beacons.isEmpty
? Center(child: CircularProgressIndicator())
: ListView(
children: ListTile.divideTiles(
context: context,
tiles: _beacons.map(
(beacon) {
return ListTile(
title: Text(
beacon.proximityUUID,
style: TextStyle(fontSize: 15.0),
),
subtitle: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
child: Text(
'Major: ${beacon.major}\nMinor: ${beacon.minor}',
style: TextStyle(fontSize: 13.0),
),
flex: 1,
fit: FlexFit.tight,
),
Flexible(
child: Text(
'Accuracy: ${beacon.accuracy}m\nRSSI: ${beacon.rssi}',
style: TextStyle(fontSize: 13.0),
),
flex: 2,
fit: FlexFit.tight,
)
],
),
);
},
),
).toList(),
),
);
}
}
class _TabBroadcastingState extends State<TabBroadcasting> {
final controller = Get.find<RequirementStateController>();
final clearFocus = FocusNode();
/// 判断现在是否有开启 broadcasting。
bool broadcasting = false;
/// UUID 格式
final regexUUID = RegExp(
r'[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}');
final uuidController =
TextEditingController(text: 'CB10023F-A318-3394-4199-A8730C7C1AEC');
final majorController = TextEditingController(text: '0');
final minorController = TextEditingController(text: '0');
/// 判断权限
bool get broadcastReady =>
controller.authorizationStatusOk == true &&
controller.locationServiceEnabled == true &&
controller.bluetoothEnabled == true;
@override
void initState() {
super.initState();
controller.startBroadcastStream.listen((flag) {
if (flag == true) {
initBroadcastBeacon();
}
});
}
/// 初始化 scanning
initBroadcastBeacon() async {
await flutterBeacon.initializeScanning;
}
@override
void dispose() {
clearFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(clearFocus),
child: Obx(
() => broadcastReady != true
? Center(child: Text('Please wait...'))
: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
uuidField,
majorField,
minorField,
SizedBox(height: 16),
buttonBroadcast,
],
),
),
),
),
),
);
}
Widget get uuidField {
return TextFormField(
readOnly: broadcasting,
controller: uuidController,
decoration: InputDecoration(
labelText: 'Proximity UUID',
),
validator: (val) {
if (val == null || val.isEmpty) {
return 'Proximity UUID required';
}
if (!regexUUID.hasMatch(val)) {
return 'Invalid Proxmity UUID format';
}
return null;
},
);
}
Widget get majorField {
return TextFormField(
readOnly: broadcasting,
controller: majorController,
decoration: InputDecoration(
labelText: 'Major',
),
keyboardType: TextInputType.number,
validator: (val) {
if (val == null || val.isEmpty) {
return 'Major required';
}
try {
int major = int.parse(val);
if (major < 0 || major > 65535) {
return 'Major must be number between 0 and 65535';
}
} on FormatException {
return 'Major must be number';
}
return null;
},
);
}
Widget get minorField {
return TextFormField(
readOnly: broadcasting,
controller: minorController,
decoration: InputDecoration(
labelText: 'Minor',
),
keyboardType: TextInputType.number,
validator: (val) {
if (val == null || val.isEmpty) {
return 'Minor required';
}
try {
int minor = int.parse(val);
if (minor < 0 || minor > 65535) {
return 'Minor must be number between 0 and 65535';
}
} on FormatException {
return 'Minor must be number';
}
return null;
},
);
}
Widget get buttonBroadcast {
final ButtonStyle raisedButtonStyle = ElevatedButton.styleFrom(
onPrimary: Colors.white,
primary: broadcasting ? Colors.red : Theme.of(context).primaryColor,
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
);
return ElevatedButton(
style: raisedButtonStyle,
onPressed: () async {
if (broadcasting) {
await flutterBeacon.stopBroadcast();
} else {
await flutterBeacon.startBroadcast(BeaconBroadcast(
proximityUUID: uuidController.text,
major: int.tryParse(majorController.text) ?? 0,
minor: int.tryParse(minorController.text) ?? 0,
));
}
final isBroadcasting = await flutterBeacon.isBroadcasting();
if (mounted) {
setState(() {
broadcasting = isBroadcasting;
});
}
},
child: Text('Broadcast${broadcasting ? 'ing' : ''}'),
);
}
}
<<: [ 卡卡 DAY 23 ] - React Native 表单套件用 Formik + Yup 验证 (上)
>>: ITHOME IRONMAN体验 Day 30-完赛心得
YAML(/ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达资料序列化的格式。YA...
人的科技文明发展始终来自於人性 在现今的科技加速之下,所有的一切都将因为有了网路而有所不同,也因为在...
大家好, 因为工作不太常用到AI/ML, 所以我自身会想要去多看多了解, 才不会脱钩 想当初整整研究...
除了文字讯息以外, Line 还有很多种讯息型态可以传送,例如图片,音档,贴图......。 我们可...