You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 09:02:44