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

如何在Flutter容器中嵌入条形码/二维码扫描器?

实现内嵌式条码扫描器(Flutter Web/移动端)

你之前使用的flutter_barcode_scanner、simple_barcode_scanner等库都是封装好的全屏扫描页面,无法直接嵌入自定义容器。要实现类似移动购物APP的内嵌式扫描效果,推荐使用mobile_scanner库——它支持将扫描视图作为普通Widget嵌入任意布局容器,同时兼容Flutter Web和移动端。

步骤1:添加依赖

在pubspec.yaml中引入最新稳定版依赖:

dependencies:
  mobile_scanner: ^5.0.0

步骤2:替换为内嵌式扫描组件

以下是修改后的代码,实现了在指定容器内显示扫描视图,同时保留你需要的边框、圆角等样式:

import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

class Scanner extends StatefulWidget {
  final double width;
  final double height;
  final int borderRadius;
  final int borderWidth;
  final Color borderColor;

  const Scanner({
    super.key,
    required this.width,
    required this.height,
    required this.borderRadius,
    required this.borderWidth,
    required this.borderColor,
  });

  @override
  _ScannerState createState() => _ScannerState();
}

class _ScannerState extends State<Scanner> {
  String _result = '';
  late MobileScannerController _controller;
  bool _isScanning = true;

  @override
  void initState() {
    super.initState();
    _controller = MobileScannerController(
      detectionSpeed: DetectionSpeed.normal,
      facing: CameraFacing.back,
      torchEnabled: false,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 内嵌扫描容器
        Container(
          width: widget.width,
          height: widget.height,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(widget.borderRadius.toDouble()),
            border: Border.all(
              color: widget.borderColor,
              width: widget.borderWidth.toDouble(),
            ),
            overflow: Clip.antiAlias, // 确保扫描视图跟随圆角裁剪
          ),
          child: Stack(
            children: [
              // 核心扫描视图
              MobileScanner(
                controller: _controller,
                onDetect: (capture) {
                  final List<Barcode> barcodes = capture.barcodes;
                  if (barcodes.isNotEmpty && _isScanning) {
                    setState(() {
                      _result = barcodes.first.rawValue ?? '未识别';
                      _isScanning = false; // 停止扫描避免重复识别
                    });
                    // 可选:延迟后恢复扫描
                    Future.delayed(const Duration(seconds: 2), () {
                      setState(() => _isScanning = true);
                    });
                  }
                },
              ),
              // 添加扫描框遮罩(模拟APP扫描样式)
              CustomPaint(
                painter: ScannerOverlayPainter(
                  borderColor: widget.borderColor,
                  borderWidth: widget.borderWidth,
                  borderRadius: widget.borderRadius,
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 20),
        // 扫描结果展示
        Text('扫描结果:$_result', style: const TextStyle(fontSize: 16)),
        // 手动控制扫描按钮(可选)
        ElevatedButton(
          onPressed: () {
            setState(() {
              _isScanning = !_isScanning;
            });
          },
          child: Text(_isScanning ? '暂停扫描' : '继续扫描'),
        ),
      ],
    );
  }
}

// 自定义扫描框遮罩 painter
class ScannerOverlayPainter extends CustomPainter {
  final Color borderColor;
  final int borderWidth;
  final int borderRadius;

  ScannerOverlayPainter({
    required this.borderColor,
    required this.borderWidth,
    required this.borderRadius,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final scanAreaWidth = size.width * 0.7;
    final scanAreaHeight = size.height * 0.7;
    final left = (size.width - scanAreaWidth) / 2;
    final top = (size.height - scanAreaHeight) / 2;

    // 绘制半透明遮罩
    final paint = Paint()
      ..color = Colors.black.withOpacity(0.5)
      ..style = PaintingStyle.fill;
    canvas.drawRect(Offset.zero & size, paint);

    // 裁剪扫描区域
    final path = Path()
      ..addRRect(RRect.fromRectAndRadius(
        Rect.fromLTWH(left, top, scanAreaWidth, scanAreaHeight),
        Radius.circular(borderRadius.toDouble()),
      ))
      ..addRect(Offset.zero & size)
      ..fillType = PathFillType.evenOdd;
    canvas.drawPath(path, paint);

    // 绘制扫描框边框
    final borderPaint = Paint()
      ..color = borderColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth.toDouble();
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(left, top, scanAreaWidth, scanAreaHeight),
        Radius.circular(borderRadius.toDouble()),
      ),
      borderPaint,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

关键说明

  • 内嵌核心:MobileScanner是一个普通Widget,可以直接放入Container等布局组件中,无需跳转新页面。
  • 扫描遮罩:通过CustomPaint绘制半透明遮罩和扫描框,模拟主流APP的扫描视觉效果。
  • 权限处理:Flutter Web中会自动请求摄像头权限;移动端需在AndroidManifest.xml/Info.plist中添加摄像头权限配置。
  • 扫描控制:通过_isScanning变量控制是否处理扫描结果,避免重复识别。

替代方案

如果mobile_scanner不符合需求,还可以尝试:

  • barcode_scan2:支持自定义扫描界面,需自行搭建布局;
  • 基于camera库+mlkit_barcode_scanning:手动实现摄像头预览+条码识别,灵活性最高,但代码量更大。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:53:23