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

Flutter自定义Camera Controller下指定区域扫码功能求助

解决方案

核心思路

用官方camera包维护自定义CameraController,搭配flutter_zxing库单独解析指定区域的帧数据,无需额外相机控制器。

步骤与代码示例

  1. 添加依赖
    在pubspec.yaml中加入:
dependencies:
  camera: ^0.10.5+5
  flutter_zxing: ^0.1.10
  1. 实现核心逻辑
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart';

class CustomQRScanner extends StatefulWidget {
  const CustomQRScanner({super.key});

  @override
  State<CustomQRScanner> createState() => _CustomQRScannerState();
}

class _CustomQRScannerState extends State<CustomQRScanner> {
  late CameraController _cameraController;
  bool _isCameraInitialized = false;
  final GlobalKey _scanBoxKey = GlobalKey();
  String? _scannedResult;

  @override
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    _cameraController = CameraController(
      cameras[0], // 后置摄像头
      ResolutionPreset.medium,
      enableAudio: false,
    );

    await _cameraController.initialize();
    if (!mounted) return;

    // 监听图像流
    _cameraController.startImageStream((image) async {
      if (_scannedResult != null) return; // 避免重复解析
      await _processCameraImage(image);
    });

    setState(() => _isCameraInitialized = true);
  }

  Future<void> _processCameraImage(CameraImage image) async {
    // 获取扫码框的位置与尺寸
    final renderBox = _scanBoxKey.currentContext?.findRenderObject() as RenderBox?;
    if (renderBox == null) return;

    final scanBoxRect = renderBox.localToGlobal(Offset.zero) & renderBox.size;
    final previewSize = _cameraController.value.previewSize!;

    // 转换坐标系(匹配预览与相机帧的尺寸比例)
    final scale = MediaQuery.of(context).size.width / previewSize.width;
    final scaledScanRect = Rect.fromLTWH(
      scanBoxRect.left / scale,
      scanBoxRect.top / scale,
      scanBoxRect.width / scale,
      scanBoxRect.height / scale,
    );

    // 将CameraImage转为Uint8List(适配flutter_zxing输入格式)
    final bytes = await _convertCameraImageToBytes(image);
    if (bytes == null) return;

    // 解析指定区域的二维码
    final result = await FlutterZxing.decode(
      bytes,
      width: previewSize.width.toInt(),
      height: previewSize.height.toInt(),
      cropRect: Rect.fromLTRB(
        scaledScanRect.left,
        scaledScanRect.top,
        scaledScanRect.left + scaledScanRect.width,
        scaledScanRect.top + scaledScanRect.height,
      ),
    );

    if (result.isValid && result.text.isNotEmpty) {
      setState(() => _scannedResult = result.text);
      await _cameraController.stopImageStream(); // 停止流避免重复解析
      // 此处可添加结果处理逻辑,如弹窗、页面跳转等
    }
  }

  Future<Uint8List?> _convertCameraImageToBytes(CameraImage image) async {
    final plane = image.planes[0];
    final uiImage = await ui.Image.fromBytes(
      image.width,
      image.height,
      plane.bytes,
      format: ui.ImageFormat.yuv420,
      rowBytes: plane.bytesPerRow,
    );

    final byteData = await uiImage.toByteData(format: ui.ImageByteFormat.png);
    return byteData?.buffer.asUint8List();
  }

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

  @override
  Widget build(BuildContext context) {
    if (!_isCameraInitialized) {
      return const Scaffold(body: Center(child: CircularProgressIndicator()));
    }

    return Scaffold(
      body: Stack(
        children: [
          CameraPreview(_cameraController),
          // 自定义扫码框,可自由调整尺寸、样式
          Center(
            child: Container(
              key: _scanBoxKey,
              width: 250,
              height: 250,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.green, width: 2),
                borderRadius: BorderRadius.circular(12),
              ),
            ),
          ),
          // 扫码结果展示
          if (_scannedResult != null)
            Positioned(
              top: 50,
              left: 20,
              right: 20,
              child: Container(
                padding: const EdgeInsets.all(16),
                color: Colors.white.withOpacity(0.9),
                child: Text('扫码结果: $_scannedResult'),
              ),
            ),
        ],
      ),
    );
  }
}

关键说明

  • 全程使用自定义CameraController,无额外相机实例开销
  • 通过flutter_zxing的cropRect参数精准限定扫码区域,只解析框内内容
  • 加入重复解析判断,避免不必要的性能消耗
  • 扫码框可通过Container参数自由调整位置、大小和视觉样式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 05:48:19