前言

本篇文章首要介绍 permission_handler、flutter_blue运用,事例中会运用 image_picker、flutter_progress_hud,因而后边两个也会简略介绍下

image_picker(挑选相片或许摄影库)

flutter_progress_hud(hud加载库)

permission_handler(权限恳求判别库)

flutter_blue(蓝牙库)]

image_gallery_saver(单纯的保存图片到相册)

事例demo

蓝牙播送外设demo-swift版别:假如想念 Object-C 版别,前面也有,这个就是那个翻译改动而来

flutter_progress_hud、image_picker

先简略介绍一下这两个,一个是加载框(flutter_progress_hud),一个是图片挑选器(支撑相片和图片)(flutter_progress_hud)

flutter_progress_hud

运用比较简略不多说了,用的也比较多,一般恳求网络数据运用,假如还有不满足,能够查找一下其他的,这个仅仅最基础的

Scaffold(
  appBar: AppBar(
    title: const Text("ProgressHUD"),
  ),
  //嵌套在外层,展现加载框时会在中间,并盖住
  //里边有些特点能够微调,能够点进去查看
  body: ProgressHUD(
    child: Container(),
  ),
);

显现或许躲藏

//显现HUD
ProgressHUD.of(context)?.show();
//显现带文字的HUD
ProgressHUD.of(context)?.showWithText(text);
//躲藏HUD
ProgressHUD.of(context)?.dismiss();

简略看一下效果吧

flutter-permission_handler、flutter_blue(蓝牙使用)、image_picker、flutter_progress_hud

image_picker

这个用的也比较多,摄影、相册挑选图片,一般只需用户头像、上传反应的都会用到,也比较常见

import 'package:image_picker/image_picker.dart';
//声明参数
final ImagePicker _picker = ImagePicker();

运用相机摄影相片

_picker.pickImage(source: ImageSource.camera).then((value) {
  print(value);
}).catchError((error) {
    //失利了走这儿,能够在这儿判别权限
    Permission.camera.status
});

运用相册挑选图片

_picker.pickImage(source: ImageSource.gallery).then((value) {
  print(value);
}).catchError((error) {
  //留意相册运用的是这个权限
  Permission.photos.status
});

能够看到image_picker运用过程中设计到了权限,后边会给出权限的运用

permission_handler

介绍权限的运用,会给出几个事例,一同也是蓝牙运用时判别权限时会用到的一个库

permission_handler(权限恳求判别库)

参数与基础运用

默许的权限状况

//常见的有下面几种状况
/*
denied, //没授权默许是这个,也保禁绝特殊情况是表明回绝的,最好是先恳求权限后用于判别
granted, //正常运用
restricted, //被操作体系回绝,例如家长操控等
limited, //被约束了部分功用,适用于部分权限,例如相册的
permanentlyDenied, //这个权限表明永久回绝,不显现弹窗,用户能够手动调理(也有或许是体系关闭了该权限导致的)
*/

恳求权限,在运用前,能够经过 request 来恳求权限,防止有些权限没办法给出准确提示,且或许犯错

//提前恳求权限,假如没给过权限,能够触发权限,以便于获取权限信息
final status = await Permission.camera.request(); //恳求单个权限
Map<Permission, PermissionStatus> statuses = await [
  Permission.photosAddOnly,
  Permission.photos
].request(); //一同恳求多个全新啊
//能够一同恳求多个 await,被约束的部分权限理论也能够运用才是,依据情况作出判别
if (status != PermissionStatus.granted) {
  //无相机权限,请前往设置翻开
  //openAppSettings();
  return;
}
//运用对应功用即可
await _picker.pickImage(source: ImageSource.camera)

直接获取权限,可是初度运用并不会触发对应的权限恳求弹窗,因而或许判别犯错,适用于运用对应功用失利后给出相应的提示

//直接摄影获取图片
_picker.pickImage(source: ImageSource.camera).then((value) {
  print(value);
}).catchError((error) {
  //或许用户回绝了权限,因而会走到这儿
  print(error);
  //能够恳求用失利的时分在提示权限问题,或许翻开授权页面
  Permission.camera.status.then((status) {
    print(status);
    if (status == PermissionStatus.denied || status == PermissionStatus.permanentlyDenied) {
      //回绝,能够跳转权限页面
      showToast(context, "相机权限被回绝");
    }else if (status == PermissionStatus.granted) {
      //正常访问
      showToast(context, "相机权限能够正常运用");
    }
  });
});

留意:不是一切的三方没给权限都会走到catch,也或许直接报错闪退,因而最好先判别权限后给出提示

//简略给出几个常用权限,里边还有很多
Map<Permission, PermissionStatus> statuses = await [
  Permission.camera, //相机
  Permission.photosAddOnly, //写入相册
  Permission.photos, //读取相册
  Permission.locationWhenInUse, //运用运用期间获取定位
  Permission.bluetooth, //获取蓝牙
].request();

跳转权限操作界面

假如用户没翻开权限,能够提示给用户翻开权限,下面能够跳转到运用的权限界面,便利用户更新权限

openAppSettings();

android权限声明

需求留意的是,运用什么权限查找一下即可,运用如下所示(需求留意的是,其有些权限跟ios端是不一样的,例如)

flutter-permission_handler、flutter_blue(蓝牙使用)、image_picker、flutter_progress_hud

<uses-permission android:name="android.permission.CAMERA" />
<!--相册的读写权限,跟ios端不一样哈,其他的有些也是,依据平台动态调整即可-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

ios端权限声明

info.plist 中设置对应权限即可,$(PRODUCT_BUNDLE_NAME)为app的姓名,动态最好

flutter-permission_handler、flutter_blue(蓝牙使用)、image_picker、flutter_progress_hud

<string>$(PRODUCT_BUNDLE_NAME)想在app运用期间获取您的定位,以便于更新方位</string>
<key>NSBluetoothPeripheralUsageDescription</key>

此外,还需求在 podfile 中参加下面的脚本(放到最后即可),依据对应权限需求,将 PERMISSION_???=1 前面的注释 # 去掉即可(上面的不要撤销),这儿假定去掉的是 camera

post_install do |installer|
  installer.pods_project.targets.each do |target|
    ... # Here are some configurations automatically generated by flutter
    # Start of the permission_handler configuration
    target.build_configurations.each do |config|
      # You can enable the permissions needed here. For example to enable camera
      # permission, just remove the `#` character in front so it looks like this:
      #
      # ## dart: PermissionGroup.camera
      # 'PERMISSION_CAMERA=1'
      #
      #  Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',
        ## dart: PermissionGroup.calendar
        # 'PERMISSION_EVENTS=1',
        ## dart: PermissionGroup.reminders
        # 'PERMISSION_REMINDERS=1',
        ## dart: PermissionGroup.contacts
        # 'PERMISSION_CONTACTS=1',
        ## dart: PermissionGroup.camera
        ## 假定这儿面用到了相机权限,只撤销掉其前面的注释即可
        'PERMISSION_CAMERA=1',
        ## dart: PermissionGroup.microphone
        # 'PERMISSION_MICROPHONE=1',
        ## dart: PermissionGroup.speech
        # 'PERMISSION_SPEECH_RECOGNIZER=1',
        ## dart: PermissionGroup.photos
        # 'PERMISSION_PHOTOS=1',
        ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
        # 'PERMISSION_LOCATION=1',
        ## dart: PermissionGroup.notification
        # 'PERMISSION_NOTIFICATIONS=1',
        ## dart: PermissionGroup.mediaLibrary
        # 'PERMISSION_MEDIA_LIBRARY=1',
        ## dart: PermissionGroup.sensors
        # 'PERMISSION_SENSORS=1',   
        ## dart: PermissionGroup.bluetooth
        # 'PERMISSION_BLUETOOTH=1',
        ## dart: PermissionGroup.appTrackingTransparency
        # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
        ## dart: PermissionGroup.criticalAlerts
        # 'PERMISSION_CRITICAL_ALERTS=1'
      ]
    end 
    # End of the permission_handler configuration
  end
end

再详细点的权限,都能够经过查找一下获取即可,详细的就不多讲述了(ios的文档给出了一些,先贴出来便利大家运用吧)

Permission Info.plist Macro
PermissionGroup.calendar NSCalendarsUsageDescription PERMISSION_EVENTS
PermissionGroup.reminders NSRemindersUsageDescription PERMISSION_REMINDERS
PermissionGroup.contacts NSContactsUsageDescription PERMISSION_CONTACTS
PermissionGroup.camera NSCameraUsageDescription PERMISSION_CAMERA
PermissionGroup.microphone NSMicrophoneUsageDescription PERMISSION_MICROPHONE
PermissionGroup.speech NSSpeechRecognitionUsageDescription PERMISSION_SPEECH_RECOGNIZER
PermissionGroup.photos NSPhotoLibraryUsageDescription PERMISSION_PHOTOS
PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription PERMISSION_LOCATION
PermissionGroup.notification PermissionGroupNotification PERMISSION_NOTIFICATIONS
PermissionGroup.mediaLibrary NSAppleMusicUsageDescription, kTCCServiceMediaLibrary PERMISSION_MEDIA_LIBRARY
PermissionGroup.sensors NSMotionUsageDescription PERMISSION_SENSORS
PermissionGroup.bluetooth NSBluetoothAlwaysUsageDescription, NSBluetoothPeripheralUsageDescription PERMISSION_BLUETOOTH
PermissionGroup.appTrackingTransparency NSUserTrackingUsageDescription PERMISSION_APP_TRACKING_TRANSPARENCY
PermissionGroup.criticalAlerts PermissionGroupCriticalAlerts PERMISSION_CRITICAL_ALERTS

flutter_blue 与 蓝牙权限

官方供给的蓝牙库,运用非常简略,运用前需求先判别权限

:ios体系有些版别是没有权限弹窗的,声明了就能够直接运用,但不代表不需求判别恳求了,后边大多数版别仍是需求用到的

flutter_blue(蓝牙库)

外设端demo:被衔接的外设播送端,由swift编写,前面也有object-c编写的版别

蓝牙播送外设demo-swift版别:依据上面的播送端代码翻译改动而来(都是自己的)

本事例demo:中心设备端,在本事例中的bluetooth文件中

PS: 为什么没有 flutter 的播送端事例,因为没供给,flutter 跨平台作为一个中心设备衔接现已很给力了,现在蓝牙对android、ios支撑不错,对鸿蒙支撑很拉跨

蓝牙权限装备

ios端装备

这儿面装备就比较固定,只不过有一个alway权限的,表明答应后台持续运行蓝牙,假如不是有必要的话无需填写,否则易形成审核问题,现在无需恳求定位,也不用翻开即可正常运用蓝牙

info.plist

<key>NSBluetoothAlwaysUsageDescription</key>
<string>$(PRODUCT_BUNDLE_NAME)想运用您的蓝牙,以便于持续跟设备衔接发送信息</string>
<key>NSBluetoothPeripheralUsageDescription</key>
   <string>$(PRODUCT_BUNDLE_NAME)想运用您的蓝牙,以便于跟设备发送信息</string>

podfile

//撤销这一行的注释即可
'PERMISSION_BLUETOOTH=1',

安卓端装备

安卓端还有点不一样,android 12 后,推出新权限,能够在在 manifest.xml 中填写如下代码,也能够在代码中动态恳求

前面两个是兼容 android12 之前的版别,所以约束了最大版别,后边则是分隔两个,需求留意的是,现在都需求恳求定位权限了,否则功用无法正常运用

manifest.xml

<!-- android6之前运用该权限即可 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
    android:maxSdkVersion="30" />
<!-- Required if your app derives physical location from Bluetooth scan results.-->
<!-- android6之后需求运用蓝牙也要声明定位权限,且用户还要翻开 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Request legacy Bluetooth permissions on older devices.-->
<!-- android6~12 之间运用该权限-->
<uses-permission android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />
<!-- android12 之后权限分隔-->
<!-- android12 扫描周边设备需求该权限-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- android12 衔接交互运用的权限-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- android12 播送功用,让别人也能衔接搞设备,一般和connect一同运用-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

build.gradle

Android {
  defaultConfig {
     minSdkVersion: 19

flutter 端中心设备运用

设备最为中心设备扫描周边设备,大致经过了下面几个阶段:

扫描 -> 衔接 -> 查找服务 -> 查找特征 -> 运用特征进行交互

细节直接看代码即可,下面简略上一个权限的判别,android 实践还要恳求定位,即:

ios 恳求一个 bluetooth 权限,android 恳求 bluetooth、bluetoothScan、bluetoothConnect、locationWhenInUse四个权限,自己动态设置即可

ps:除了要判别权限还有蓝牙是否翻开,此外这儿面权限判别不如原生端准确,一些体系版别的不需求权限,可是这儿或许会返回回绝,因而最好给这些特殊版别一些提示,以便于"没给权限"也能持续履行(否则他们永久无法运用蓝牙了,只能更新体系)

//仅仅是一个事例,实践能够写的严谨简略一些,先判别翻开,后判别权限都能够,扫描前检测即可
//恳求一下权限,某些机型蓝牙默许能够直接运用,无需权限,可是权限会反应被回绝状况
//因而一些机型,能够恳求后给出持续向后履行的弹窗,防止用户永久无法运用该功用,失利了留意反应即可
[Permission.bluetoothScan, Permission.bluetoothConnect, Permission.bluetooth].request().then((status) {
  print(status);
  //只会有一个弹窗,android端多个权限,但一般一般显现了一个就完毕了,首要看ios的
  //android端留意定位权限, Permission.locationWhenInUse 即可
  if (status[Permission.bluetooth] != PermissionStatus.granted) {
    print("没有蓝牙权限"); //这一个是都有的,能够用这个判别,当然最好分平台判别
  }
});
//还要判别蓝牙是否现已翻开
_bluetooth.isOn.then((value) {
  //获取蓝牙翻开状况
}); 

扫描、衔接、查找服务和特征逻辑如下所示,此外还设置类的告诉,以便于能够及时收到音讯

//开端扫描并衔接设备
void startConnectDevice({isRepeat = false}) {
  bool isSearched = false; //为了防止衔接过程中没有中止扫描导致的屡次衔接问题,但不得不衔接后在撤销扫描
  try {
    _bluetooth.scanResults.listen((results) async {
      //扫描回调,try-catch是统一处理里边的失利情况
      if (isSearched) return; //现已找到了就完毕
      for (ScanResult res in results) {
        final device = res.device;
        //检查是否是咱们需求的设备,device.name有时分不一定会有,最好运用advertisementData.localName
        final name = res.advertisementData.localName;
        if (name != "") print(name);
        if (!isSearched && name.contains("marshal_")) {
          print("找到了咱们需求的设备");
          isSearched = true; //标记现已找到了
          //找到了咱们要衔接的设备,并衔接,autoConnect需求设置为 false,默许为true其不会主动衔接
          print("开端衔接");
          //鸿蒙这个 autoConnect 会一次也衔接不上,设置为 false 则会呈现时而连得上时而连不上,支撑不太好,依据情况挑选
          await device.connect(timeout: const Duration(seconds: 10), autoConnect: true);
          print("衔接成功");
          // 衔接成功后中止扫描,听说有些android手机中止扫描会呈现衔接功用反常,衔接成功后在中止扫描
          await _bluetooth.stopScan();
          //开端查找服务
          print("开端查找服务");
          List<BluetoothService> services = await device.discoverServices();
          //遍历服务运用咱们想要的特征值
          print("遍历服务找特征值");
          print(services.length);
          services.forEach((service) async {
            var characteristics = service.characteristics;
            //找咱们的特征值
            for(BluetoothCharacteristic char in characteristics) {
              print(char.uuid.toString());
              //经过uuid过滤出咱们需求的特征值
              if (char.uuid.toString().substring(0, 2) == "12") {
                print("找到咱们特征值");
                //假定咱们用这个,获取到特征值后保存,能够用来读写
                _currentCharacteristic = char;
                await setNotifiy();//设置告诉,能够接纳传递过来的音讯
                await readMessage(); //随意读取一下音讯吧
              }
            }
          });
        }
      }
    });
  }catch(error) {
    print(error);
    isSearched = false;
    print('一般衔接失利后会走这儿,然后中止扫描');
    _bluetooth.stopScan();
  }
  print("开端扫描");
  _bluetooth.startScan(scanMode: ScanMode.balanced, timeout: const Duration(seconds: 30),);
}

接下来看看读写音讯代码,比较简略,需求留意的是,发送接纳的字符串一般运用 utf8 转化

此外,假如传输数据量比较大的话,需求屡次传递就行了(因为设备缘故一次不能传递数据过,因为传输长度约束问题,单次长度主张跟 UUID 长度一样16字节最佳,当然自己能够尝试更长的字符串(一般超越20字节就会丢失了))
//设置告诉
Future<void> setNotifiy() async {
  //能够监听外设端主动发送过来的音讯
  await _currentCharacteristic!.setNotifyValue(true);
  _currentCharacteristic!.value.listen((event) {
    print(event);
  });
}
//读取外设音讯
Future<void> readMessage() async {
  if (_currentCharacteristic == null) return;
  List<int> value = await _currentCharacteristic!.read();
  print("value");
  print(value);
  final string = utf8.decode(value);
  print(string);
}
//向外设发送音讯
void sendMessage(String text) {
  print(text);
  _controller.clear();
  _currentCharacteristic?.write(utf8.encode(text)).then((value) => print(value));
}

最后

快来尝试一下吧,两个事例都是通的,假如想玩更有趣的,能够更新两头代码,彼此交互,写一个情侣间的小型传输工具也不是未尝不可