基于Flutter实现类似GooglePay的二维码扫描器Overlay
实现Google Pay风格的二维码扫描Overlay组件(Flutter)
既然mobile_scanner包本身不提供Overlay,我们可以完全自定义一个适配Google Pay样式的Overlay组件,和mobile_scanner配合使用即可,不需要依赖停止维护的包。
核心实现思路
用Stack组件将MobileScanner作为底层扫描视图,在其上方叠加自定义的Overlay:
- 底层是
MobileScanner负责扫码逻辑 - 上层是自定义组件,包含:
- 半透明遮罩(覆盖扫描框以外的区域)
- 四个不同颜色的边角标记(模拟Google Pay的样式)
- 可选的扫描线动画(还原完整效果)
完整代码示例
1. 主扫码页面结构
import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class GooglePayStyleQrScanner extends StatefulWidget { const GooglePayStyleQrScanner({super.key}); @override State<GooglePayStyleQrScanner> createState() => _GooglePayStyleQrScannerState(); } class _GooglePayStyleQrScannerState extends State<GooglePayStyleQrScanner> { MobileScannerController cameraController = MobileScannerController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('扫码付款')), body: Stack( children: [ // 底层扫码视图 MobileScanner( controller: cameraController, onDetect: (capture) { final List<Barcode> barcodes = capture.barcodes; // 处理扫码结果逻辑 if (barcodes.isNotEmpty) { print('扫码结果: ${barcodes.first.rawValue}'); // 这里可以调用你的showModalBottomSheet逻辑 } }, ), // 上层自定义Overlay const QrScannerOverlay(), ], ), ); } }
2. 自定义Overlay组件(核心)
class QrScannerOverlay extends StatelessWidget { const QrScannerOverlay({super.key}); // 扫描框的尺寸(占屏幕宽度的70%) final double scanBoxSize = 0.7; // 边角的宽度和长度 final double cornerWidth = 4.0; final double cornerLength = 24.0; @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; final boxSize = screenSize.width * scanBoxSize; return Stack( children: [ // 半透明遮罩层 Positioned.fill( child: Container( color: Colors.black.withOpacity(0.5), child: Center( // 中间挖空扫描框 child: SizedBox( width: boxSize, height: boxSize, child: const ColoredBox(color: Colors.transparent), ), ), ), ), // 四个颜色边角 Positioned( top: (screenSize.height - boxSize) / 2, left: (screenSize.width - boxSize) / 2, child: CustomPaint( size: Size(boxSize, boxSize), painter: _CornerPainter( cornerWidth: cornerWidth, cornerLength: cornerLength, cornerColors: const [ Colors.blue, // 左上角 Colors.green, // 右上角 Colors.yellow, // 右下角 Colors.red, // 左下角 ], ), ), ), // 可选:扫描线动画 Positioned( top: (screenSize.height - boxSize) / 2 + 10, left: (screenSize.width - boxSize) / 2 + 10, child: AnimatedBuilder( animation: const AlwaysStoppedAnimation(true), builder: (context, child) { return SizedBox( width: boxSize - 20, child: const LinearProgressIndicator( color: Colors.white, backgroundColor: Colors.transparent, ), ); }, ), ), ], ); } } // 自定义画笔绘制四个颜色边角 class _CornerPainter extends CustomPainter { final double cornerWidth; final double cornerLength; final List<Color> cornerColors; _CornerPainter({ required this.cornerWidth, required this.cornerLength, required this.cornerColors, }); @override void paint(Canvas canvas, Size size) { final paint = Paint()..style = PaintingStyle.stroke; // 左上角 paint.color = cornerColors[0]; paint.strokeWidth = cornerWidth; canvas.drawLine(Offset(0, 0), Offset(cornerLength, 0), paint); canvas.drawLine(Offset(0, 0), Offset(0, cornerLength), paint); // 右上角 paint.color = cornerColors[1]; canvas.drawLine(Offset(size.width, 0), Offset(size.width - cornerLength, 0), paint); canvas.drawLine(Offset(size.width, 0), Offset(size.width, cornerLength), paint); // 右下角 paint.color = cornerColors[2]; canvas.drawLine(Offset(size.width, size.height), Offset(size.width - cornerLength, size.height), paint); canvas.drawLine(Offset(size.width, size.height), Offset(size.width, size.height - cornerLength), paint); // 左下角 paint.color = cornerColors[3]; canvas.drawLine(Offset(0, size.height), Offset(cornerLength, size.height), paint); canvas.drawLine(Offset(0, size.height), Offset(0, size.height - cornerLength), paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }
关键细节说明
- 遮罩层通过
Positioned.fill覆盖全屏,中间用透明SizedBox实现挖空效果,模拟Google Pay的半透明背景 - 四个边角通过
CustomPainter绘制,每个边角使用不同颜色,完全匹配Google Pay的样式 - 扫描框尺寸设置为屏幕宽度的70%,你可以根据需求调整
scanBoxSize参数 - 可选的扫描线动画用
LinearProgressIndicator实现,也可以替换成自定义的滑动线条动画
这样实现的Overlay完全独立于扫码包,你可以直接和mobile_scanner配合使用,不需要依赖停止维护的qr_code_scanner。
内容的提问来源于stack exchange,提问作者Nipul Rathod
相关产品推荐
相关产品推荐

