如何快速完美地实现Flutter扫码
效果预览
笔者非常执着于能使用直观的效果展示,但是因为CSDN的大小限制,笔者做了很强的压缩,大致还是能看出代码的最终结果
实现需求
实现需求 | 是否实现 |
---|---|
自定义扫码区域大小(可全屏) | ✅ |
连续扫码 | ✅ |
从相册选择 | ✅ |
支持二维码 | ✅ |
支持条形码 | ✅ |
扫描动画特效 | ✅ |
Android 平台兼容 | ✅ |
iOS 平台兼容 | ✅ |
环境
Flutter版本 1.22.0.stable
pubspec.yaml中添加依赖
scan: 0.0.7
images_picker: 0.0.8
其中scan插件用于扫码,images_picker插件用于从相册选择二维码或者条形码图片
代码实现
前置条件
既然是扫码,肯定会访问到设备的摄像头,所以需要申请到相机的权限
至于权限申请的部分可以参考我的另一篇博文 Flutter Android权限问题
Dart代码部分
import 'package:flutter/material.dart';
import 'package:images_picker/images_picker.dart';
import 'package:scan/scan.dart';
/// 扫码页面
class QRScannerPage extends StatefulWidget {
final QRScannerPageConfig config;
const QRScannerPage({this.config});
@override
_QRScannerPageState createState() => _QRScannerPageState();
}
class _QRScannerPageState extends State<QRScannerPage> {
StateSetter stateSetter;
IconData lightIcon = Icons.flash_on;
ScanController controller = ScanController();
List<String> result = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('扫码'),
),
body: _buildBody());
}
Widget _buildBody() {
return Stack(children: [
ScanView(
controller: controller,
scanAreaScale: widget.config.scanAreaSize,
scanLineColor: widget.config.scanLineColor,
onCapture: (String data) async {
await showResult(content: '扫码结果\t$data');
controller.resume();
},
),
Positioned(
left: 50,
bottom: 100,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
stateSetter = setState;
return MaterialButton(
child: Icon(lightIcon, size: 30, color: Colors.greenAccent),
onPressed: () {
controller.toggleTorchMode();
if (lightIcon == Icons.flash_on) {
lightIcon = Icons.flash_off;
} else {
lightIcon = Icons.flash_on;
}
stateSetter(() {});
});
},
),
),
Positioned(
right: 50,
bottom: 100,
child: MaterialButton(
child: Icon(Icons.image,
size: 30, color: Color.fromRGBO(4, 184, 67, 1)),
onPressed: () async {
await pickImage();
// DialogUtil.showCommonDialog(context, '$result');
}),
),
]);
}
Future<void> showResult({String content}) async {
return showGeneralDialog(
context: context,
pageBuilder: (context, anim1, anim2) {},
barrierColor: Colors.black.withOpacity(.6),
barrierDismissible: true,
barrierLabel: "",
transitionDuration: Duration(milliseconds: 150),
transitionBuilder: (context, anim1, anim2, child) {
return Transform.scale(
scale: anim1.value,
child: Opacity(
opacity: anim1.value,
child: Center(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: new Material(
type: MaterialType.transparency,
child: new Container(
height: 450,
width: 300,
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
))),
child: Column(
children: [
Expanded(
flex: 2,
child: Align(
alignment: Alignment.center,
child: Text(
content,
style:
TextStyle(height: 1, fontSize: 18),
),
),
),
DividerHorizontal(),
Expanded(
flex: 1,
child: Row(
children: [
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.pop(context);
},
child: Align(
alignment: Alignment.center,
child: Text(
'确认',
style: TextStyle(
color: Color(0xFFFF7B85),
fontSize: 18),
),
),
))
],
),
)
],
)),
)),
)));
});
}
Future pickImage() async {
List<Media> files = await ImagesPicker.pick(
pickType: PickType.image,
count: 9,
);
if (files != null && files.isNotEmpty) {
for (int i = 0; i < files.length; i++) {
String value = await Scan.parse(files[i].path);
result.add(value);
}
showResult(content: result.toString());
}
}
}
class QRScannerPageConfig {
double scanAreaSize;
Color scanLineColor;
QRScannerPageConfig(
{this.scanAreaSize: 1.0,
this.scanLineColor: const Color.fromRGBO(4, 184, 67, 1)});
}
class DividerHorizontal extends StatelessWidget {
final double height;
final Color color;
DividerHorizontal({this.height: 1, this.color: const Color(0xFFF8F9F8)});
@override
Widget build(BuildContext context) {
return Container(
height: height,
color: color,
);
}
}
这个代码都是可以直接copy使用的,如果有需要可以适当地更改
项目的配置
建议Android的最低兼容要在API 21(Android 5.0)以上,iOS的最低兼容要在iOS 10以上,这个是 images_picker这个插件所要求的
下图是Android build.gradle配置
权限配置
Android权限配置
在AndroidManifest.xml文件配置相应权限,但是注意的一点:这个只是安装时的权限申请,在Android 7.0之后是需要运行时权限,所以这个问题的解决需要参考我的这篇文章 Flutter Android权限问题
其实说到底还是动态权限的问题。
下图是Android的权限配置
iOS权限配置
打开下图的配置文件(为了防止不熟悉iOS的同学找错地方,截了个图)
在 “dict” 这个标签里面添加以下代码
<key>NSAppleMusicUsageDescription</key>
<string>App需要您的同意,才能访问媒体资料库</string>
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>
这样就可以完成了对 iOS 平台的兼容,使用笔者的公司项目(代码其实差不多,只是UI稍微调整了一下)在 iPhone SE 2
iOS 13.4系统下完美运行,如下图
总结
笔者在此之前,试用了多个扫码的插件,效果要么就是无法全屏,要么就是无法出现扫码的效果,亦或者是无法连续扫码(每次扫码结束都会从扫码界面回退),总体来说这个scan插件算是比较好地满足了我的需求,后续我会专门写一篇文章来讲解:如何开发一个可供Dart调用的Android的扫码插件。
最近笔者在公司使用Flutter技术独自开发一款企业级的物联网应用,如果对笔者感兴趣,欢迎关注笔者。读者有好的建议,也欢迎在下面留言。码字不易,请给👍