Flutter扫码功能完美实现

Posted Xiao冰同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter扫码功能完美实现相关的知识,希望对你有一定的参考价值。

如何快速完美地实现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技术独自开发一款企业级的物联网应用,如果对笔者感兴趣,欢迎关注笔者。读者有好的建议,也欢迎在下面留言。码字不易,请给👍

以上是关于Flutter扫码功能完美实现的主要内容,如果未能解决你的问题,请参考以下文章

flutter 并不完美的登录完美验证功能

微信小程序使用场景延伸:扫码登录扫码支付

flutter扫码插件,支持自定义

Flutter/Dart datamatrix 扫码库

lutter 调用原生硬件 Api 实现扫码

uni-app实现扫码功能