Flutter自定义Camera Controller下指定区域扫码功能求助
解决方案
核心思路
用官方camera包维护自定义CameraController,搭配flutter_zxing库单独解析指定区域的帧数据,无需额外相机控制器。
步骤与代码示例
- 添加依赖
在pubspec.yaml中加入:
dependencies: camera: ^0.10.5+5 flutter_zxing: ^0.1.10
- 实现核心逻辑
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
相关产品推荐
相关产品推荐

