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

如何使用Stack与mobile_scanner实现中间无模糊的扫码视图

Flutter实现扫码页中心透明扫描区效果方案

你当前使用Stack叠层搭配mobile_scanner的技术选型完全可行,核心只需要在相机预览层上方加一个中间区域挖空的半透明遮罩即可,不需要修改扫描组件本身的渲染逻辑。

核心实现步骤

  • 先固定Stack层级顺序:最底层放MobileScanner相机预览组件,中间层放置挖空遮罩,最上层放扫描框动画、操作按钮、提示文案等UI元素。
  • 不推荐手动拼接上下左右四个半透明Container拼遮罩,横竖屏切换、不同分辨率屏幕适配时很容易出现缝隙、错位问题,直接用CustomPaint通过路径差集绘制挖空遮罩兼容性最好。
  • 最后记得把MobileScanner的scanWindow识别区域参数和你视觉上的透明扫描区坐标对齐,避免出现视觉框和实际扫码识别范围不匹配的问题。

可直接复用的代码示例

挖空遮罩组件

class ScannerMaskOverlay extends StatelessWidget {
  const ScannerMaskOverlay({
    super.key,
    required this.scanAreaSize,
    this.maskColor = const Color(0x99000000),
    this.borderRadius = 12,
    this.borderWidth = 2,
    this.borderColor = Colors.blueAccent,
  });

  /// 中间透明扫描区的尺寸
  final Size scanAreaSize;
  /// 非扫描区的遮罩颜色,默认60%透明度纯黑
  final Color maskColor;
  /// 扫描框圆角
  final double borderRadius;
  /// 扫描框边框宽度
  final double borderWidth;
  /// 扫描框边框颜色
  final Color borderColor;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final scanRect = Rect.fromCenter(
          center: Offset(constraints.maxWidth / 2, constraints.maxHeight / 2),
          width: scanAreaSize.width,
          height: scanAreaSize.height,
        );
        return CustomPaint(
          painter: _ScannerMaskPainter(
            scanAreaRect: scanRect,
            maskColor: maskColor,
            borderRadius: borderRadius,
            borderWidth: borderWidth,
            borderColor: borderColor,
          ),
        );
      },
    );
  }
}

class _ScannerMaskPainter extends CustomPainter {
  _ScannerMaskPainter({
    required this.scanAreaRect,
    required this.maskColor,
    required this.borderRadius,
    required this.borderWidth,
    required this.borderColor,
  });

  final Rect scanAreaRect;
  final Color maskColor;
  final double borderRadius;
  final double borderWidth;
  final Color borderColor;

  @override
  void paint(Canvas canvas, Size size) {
    // 构建全屏遮罩路径,减去中间扫描区路径得到带透明洞的遮罩
    final fullScreenPath = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height));
    final scanHolePath = Path()
      ..addRRect(RRect.fromRectAndRadius(scanAreaRect, Radius.circular(borderRadius)));
    final finalMaskPath = Path.combine(PathOperation.difference, fullScreenPath, scanHolePath);
    
    // 绘制半透明遮罩
    canvas.drawPath(finalMaskPath, Paint()..color = maskColor);

    // 绘制扫描区边框
    canvas.drawRRect(
      RRect.fromRectAndRadius(scanAreaRect, Radius.circular(borderRadius)),
      Paint()
        ..style = PaintingStyle.stroke
        ..strokeWidth = borderWidth
        ..color = borderColor,
    );
  }

  @override
  bool shouldRepaint(covariant _ScannerMaskPainter oldDelegate) {
    return oldDelegate.scanAreaRect != scanAreaRect || oldDelegate.maskColor != maskColor;
  }
}

页面层使用示例

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      alignment: Alignment.center,
      children: [
        // 最底层:扫码相机预览
        MobileScanner(
          controller: MobileScannerController(
            // 这里的scanWindow要和你设置的扫描区位置尺寸一致,保证识别范围匹配
            // scanWindow: 对应你计算出来的scanRect
          ),
        ),
        // 中间层:带挖空的半透明遮罩+扫描框
        const ScannerMaskOverlay(
          scanAreaSize: Size(280, 280),
        ),
        // 最上层:其他UI,比如返回按钮、闪光灯开关、相册选择按钮、提示文案等
        // Positioned(
        //   top: 48,
        //   left: 16,
        //   child: BackButton(color: Colors.white),
        // ),
      ],
    ),
  );
}

扩展说明

如果需要给非扫描区加模糊效果,只需要给ScannerMaskOverlay外面套一层BackdropFilter即可,设置ImageFilter.blur(sigmaX: 3, sigmaY: 3),中间挖空的区域不会被模糊,完全不影响扫描区的预览清晰度。

内容的提问来源于stack exchange,提问作者quinzo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:45:30