如何在Flutter中实现带固定跟踪框的QR码扫描器
Flutter 实现跟随QR码的固定识别框方案
当然有开发者实现过这类功能,下面给你几个实用的实现思路和代码参考:
一、推荐依赖包
优先选择支持返回QR码边界坐标的扫描库,这类库能直接提供QR码在相机预览中的位置信息,是实现跟随框的核心:
mobile_scanner:Flutter社区维护的高性能扫描库,支持Android/iOS,能返回条码的boundingBox(边界矩形),API简洁易上手。qr_code_scanner:老牌QR扫描库,同样支持获取QR码的位置参数,文档成熟稳定。
这里以mobile_scanner为例演示,先在pubspec.yaml添加依赖:
dependencies: mobile_scanner: ^5.1.0
二、核心实现步骤
- 初始化扫描控制器,监听条码扫描回调,实时获取QR码的边界坐标
- 在相机预览层上方叠加自定义组件,根据返回的坐标动态更新框的位置和大小
- 处理相机预览与屏幕坐标系的转换,确保框的位置与实际QR码完全匹配
三、代码示例
import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class QrScannerWithOverlay extends StatefulWidget { const QrScannerWithOverlay({super.key}); @override State<QrScannerWithOverlay> createState() => _QrScannerWithOverlayState(); } class _QrScannerWithOverlayState extends State<QrScannerWithOverlay> { final MobileScannerController _controller = MobileScannerController(); Rect? _qrBoundingBox; // 存储QR码的边界矩形 Size? _previewSize; // 相机预览尺寸 @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 相机预览层 MobileScanner( controller: _controller, onScannerStarted: (size) { setState(() => _previewSize = size); }, onDetect: (capture) { final barcodes = capture.barcodes; if (barcodes.isNotEmpty) { setState(() => _qrBoundingBox = barcodes.first.boundingBox); } else { setState(() => _qrBoundingBox = null); } }, ), // 跟随QR码的识别框 if (_qrBoundingBox != null && _previewSize != null) AnimatedPositioned( duration: const Duration(milliseconds: 100), left: _convertX(_qrBoundingBox!.left), top: _convertY(_qrBoundingBox!.top), width: _convertWidth(_qrBoundingBox!.width), height: _convertHeight(_qrBoundingBox!.height), child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.greenAccent, width: 3), borderRadius: BorderRadius.circular(8), ), // 顶部标签,模拟Google Lens的样式 child: const Align( alignment: Alignment.topCenter, child: Padding( padding: EdgeInsets.only(top: 4), child: Text( "QR码", style: TextStyle( color: Colors.greenAccent, backgroundColor: Colors.black54, fontSize: 12, ), ), ), ), ), ), ], ), ); } // 相机预览X坐标转屏幕X坐标 double _convertX(double x) { final screenWidth = MediaQuery.of(context).size.width; return x * (screenWidth / _previewSize!.width); } // 相机预览Y坐标转屏幕Y坐标 double _convertY(double y) { final screenHeight = MediaQuery.of(context).size.height; return y * (screenHeight / _previewSize!.height); } // 转换宽度 double _convertWidth(double width) { final screenWidth = MediaQuery.of(context).size.width; return width * (screenWidth / _previewSize!.width); } // 转换高度 double _convertHeight(double height) { final screenHeight = MediaQuery.of(context).size.height; return height * (screenHeight / _previewSize!.height); } @override void dispose() { _controller.dispose(); super.dispose(); } }
四、优化建议
- 平滑过渡:用
AnimatedPositioned替代普通Positioned,让框的移动更流畅,避免生硬跳变 - 多码支持:如果需要同时识别多个QR码,可遍历
barcodes列表,绘制多个识别框 - 样式定制:可以给框添加阴影、闪烁动画,或者替换成自定义图标/标签样式
- 权限处理:记得在Android的
AndroidManifest.xml和iOS的Info.plist中配置相机权限申请
内容的提问来源于stack exchange,提问作者NewbieMF
相关产品推荐
相关产品推荐

