Flutter for OpenHarmony三方库适配实战:qr_code_scanner 二维码扫描
二维码扫描是移动应用的常见功能,用于支付、登录、信息获取、商品溯源等场景。在 Flutter for OpenHarmony 应用开发中,是一个功能完善的二维码扫描插件,提供了跨平台的实时扫描能力。qr_code_scanner_ohos 为 Flutter for OpenHarmony 提供了完整的二维码扫描能力,支持实时扫描、闪光灯控制、摄像头切换等功能。虽然原库已进入维护模式,但对于需要迁
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发

一、qr_code_scanner 库概述
二维码扫描是移动应用的常见功能,用于支付、登录、信息获取、商品溯源等场景。在 Flutter for OpenHarmony 应用开发中,qr_code_scanner 是一个功能完善的二维码扫描插件,提供了跨平台的实时扫描能力。
qr_code_scanner 库特点
qr_code_scanner 库基于 Flutter 平台视图实现,提供了以下核心特性:
实时扫描:通过相机预览实时检测二维码和条形码,支持多种码制格式。
原生嵌入:使用 Platform View 将原生相机视图嵌入 Flutter 界面,体验流畅自然。
相机控制:支持闪光灯开关、前后摄像头切换、相机暂停恢复等操作。
多格式支持:支持 QR Code、Code 128、Code 39、EAN-13、EAN-8、Aztec、Data Matrix 等多种条码格式。
扫描区域:支持自定义扫描区域,提高扫描效率和用户体验。
权限处理:自动处理相机权限请求,简化开发流程。
功能支持对比
| 功能 | Android | iOS | OpenHarmony |
|---|---|---|---|
| 实时扫描 | ✅ | ✅ | ✅ |
| QR Code | ✅ | ✅ | ✅ |
| 条形码 | ✅ | ✅ | ✅ |
| 闪光灯控制 | ✅ | ✅ | ✅ |
| 前后摄像头 | ✅ | ✅ | ✅ |
| 扫描区域 | ✅ | ✅ | ✅ |
| 相机暂停恢复 | ✅ | ✅ | ✅ |
| 原始字节数据 | ✅ | ✅ | ✅ |
使用场景:移动支付、扫码登录、商品溯源、票务验证、信息获取、社交分享等。
⚠️ 注意:qr_code_scanner 原库已进入维护模式,建议新项目考虑使用
mobile_scanner库。但对于已有项目迁移,qr_code_scanner_ohos 仍然是可行的选择。
二、安装与配置
2.1 添加依赖
在项目的 pubspec.yaml 文件中添加 qr_code_scanner_ohos 依赖:
dependencies:
qr_code_scanner_ohos:
git:
url: https://atomgit.com/openharmony-sig/fluttertpc_qr_code_scanner.git
path: ohos
然后执行以下命令获取依赖:
flutter pub get
2.2 权限配置
在 OpenHarmony 项目中需要配置相机权限。打开 ohos/entry/src/main/module.json5 文件,在 requestPermissions 数组中添加:
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
然后在 ohos/entry/src/main/resources/base/element/string.json 中添加权限说明:
{
"string": [
{
"name": "camera_reason",
"value": "需要相机权限来扫描二维码"
}
]
}
三、核心 API 详解
3.1 QRView 组件
QRView 是二维码扫描的核心组件,用于显示相机预览并实时检测二维码。
class QRView extends StatefulWidget {
const QRView({
required Key key, // 必需,用于获取控制器
required this.onQRViewCreated, // 创建完成回调
this.overlay, // 扫描框叠加层
this.overlayMargin = EdgeInsets.zero, // 叠加层边距
this.cameraFacing = CameraFacing.back, // 相机方向
this.onPermissionSet, // 权限设置回调
this.formatsAllowed = const <BarcodeFormat>[], // 允许的码制
});
}
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| key | GlobalKey | 必需,用于获取 QRViewController |
| onQRViewCreated | QRViewCreatedCallback | QRView 创建完成回调 |
| overlay | QrScannerOverlayShape? | 扫描框叠加层组件 |
| overlayMargin | EdgeInsetsGeometry | 叠加层边距 |
| cameraFacing | CameraFacing | 相机方向(前/后) |
| onPermissionSet | PermissionSetCallback? | 权限设置回调 |
| formatsAllowed | List | 允许扫描的码制格式 |
使用示例:
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRView(
key: qrKey,
onQRViewCreated: (controller) {
controller.scannedDataStream.listen((scanData) {
print('扫描结果: ${scanData.code}');
});
},
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: 250,
),
)
3.2 QRViewController 控制器
QRViewController 提供相机控制和扫描结果获取功能。
class QRViewController {
Stream<Barcode> get scannedDataStream; // 扫描结果流
bool get hasPermissions; // 是否有权限
Future<CameraFacing> getCameraInfo(); // 获取摄像头信息
Future<CameraFacing> flipCamera(); // 切换摄像头
Future<bool?> getFlashStatus(); // 获取闪光灯状态
Future<void> toggleFlash(); // 切换闪光灯
Future<void> pauseCamera(); // 暂停相机
Future<void> resumeCamera(); // 恢复相机
Future<void> stopCamera(); // 停止相机
Future<SystemFeatures> getSystemFeatures(); // 获取设备功能
void dispose(); // 释放资源
}
3.3 scannedDataStream 属性
scannedDataStream 是扫描结果的数据流,每次扫描到条码都会触发。
Stream<Barcode> get scannedDataStream
使用示例:
controller.scannedDataStream.listen((barcode) {
print('内容: ${barcode.code}');
print('格式: ${barcode.format}');
});
3.4 getCameraInfo 方法
getCameraInfo 方法用于获取当前摄像头信息。
Future<CameraFacing> getCameraInfo()
返回值:返回当前摄像头方向(front 或 back)。
使用示例:
final cameraInfo = await controller.getCameraInfo();
if (cameraInfo == CameraFacing.front) {
print('当前是前置摄像头');
} else {
print('当前是后置摄像头');
}
3.5 flipCamera 方法
flipCamera 方法用于切换前后摄像头。
Future<CameraFacing> flipCamera()
返回值:返回切换后的摄像头方向。
使用示例:
await controller.flipCamera();
3.6 getFlashStatus 方法
getFlashStatus 方法用于获取闪光灯状态。
Future<bool?> getFlashStatus()
返回值:返回闪光灯是否开启,不支持时返回 null。
使用示例:
final isFlashOn = await controller.getFlashStatus();
print('闪光灯状态: $isFlashOn');
3.7 toggleFlash 方法
toggleFlash 方法用于切换闪光灯开关状态。
Future<void> toggleFlash()
使用示例:
await controller.toggleFlash();
3.8 pauseCamera 方法
pauseCamera 方法用于暂停相机预览和扫描。
Future<void> pauseCamera()
使用示例:
await controller.pauseCamera();
3.9 resumeCamera 方法
resumeCamera 方法用于恢复相机预览和扫描。
Future<void> resumeCamera()
使用示例:
await controller.resumeCamera();
3.10 stopCamera 方法
stopCamera 方法用于停止相机。
Future<void> stopCamera()
使用示例:
await controller.stopCamera();
3.11 getSystemFeatures 方法
getSystemFeatures 方法用于获取设备支持的相机功能。
Future<SystemFeatures> getSystemFeatures()
返回值:返回 SystemFeatures 对象,包含设备功能信息。
使用示例:
final features = await controller.getSystemFeatures();
print('有前置摄像头: ${features.hasFrontCamera}');
print('有后置摄像头: ${features.hasBackCamera}');
print('有闪光灯: ${features.hasFlash}');
3.12 dispose 方法
dispose 方法用于释放控制器资源。
void dispose()
使用示例:
void dispose() {
controller?.dispose();
super.dispose();
}
四、数据类型详解
4.1 Barcode 类
Barcode 类封装了扫描结果信息。
class Barcode {
Barcode(this.code, this.format, this.rawBytes);
final String? code; // 扫描内容
final BarcodeFormat format; // 码制格式
final List<int>? rawBytes; // 原始字节数据
}
属性说明:
| 属性 | 类型 | 说明 |
|---|---|---|
| code | String? | 扫描到的文本内容 |
| format | BarcodeFormat | 条码格式类型 |
| rawBytes | List? | 原始字节数据 |
4.2 BarcodeFormat 枚举
BarcodeFormat 定义了支持的条码格式。
enum BarcodeFormat {
aztec, // Aztec 2D 条码
codabar, // CODABAR 1D(iOS不支持)
code39, // Code 39 1D
code93, // Code 93 1D
code128, // Code 128 1D
dataMatrix, // Data Matrix 2D
ean8, // EAN-8 1D
ean13, // EAN-13 1D
itf, // ITF 1D
maxicode, // MaxiCode 2D(iOS不支持)
pdf417, // PDF417
qrcode, // QR Code 二维码
rss14, // RSS 14(iOS不支持)
rssExpanded, // RSS EXPANDED(iOS不支持)
upcA, // UPC-A 1D
upcE, // UPC-E 1D
upcEanExtension, // UPC/EAN 扩展
unknown, // 未知格式
}
4.3 CameraFacing 枚举
CameraFacing 定义了摄像头方向。
enum CameraFacing {
back, // 后置摄像头
front, // 前置摄像头
unknown, // 未知
}
4.4 SystemFeatures 类
SystemFeatures 类包含设备相机功能信息。
class SystemFeatures {
final bool hasBackCamera; // 是否有后置摄像头
final bool hasFrontCamera; // 是否有前置摄像头
final bool hasFlash; // 是否有闪光灯
final String? activeCamera; // 当前激活的摄像头ID
}
五、Widget 组件详解
5.1 QrScannerOverlayShape 扫描框
QrScannerOverlayShape 是内置的扫描框样式组件,继承自 ShapeBorder。
class QrScannerOverlayShape extends ShapeBorder {
QrScannerOverlayShape({
this.borderColor = Colors.red, // 边框颜色
this.borderWidth = 3.0, // 边框宽度
this.overlayColor = const Color.fromRGBO(0, 0, 0, 80), // 遮罩颜色
this.borderRadius = 0, // 圆角半径
this.borderLength = 40, // 边角长度
double? cutOutSize, // 扫描区域大小(正方形)
double? cutOutWidth, // 扫描区域宽度
double? cutOutHeight, // 扫描区域高度
this.cutOutBottomOffset = 0, // 裁剪区域底部偏移
});
}
参数说明:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| borderColor | Color | Colors.red | 边框颜色 |
| borderWidth | double | 3.0 | 边框宽度 |
| overlayColor | Color | Color.fromRGBO(0,0,0,80) | 遮罩颜色 |
| borderRadius | double | 0 | 边角圆角半径 |
| borderLength | double | 40 | 边角线条长度 |
| cutOutSize | double? | 250 | 扫描区域大小 |
| cutOutWidth | double? | null | 扫描区域宽度 |
| cutOutHeight | double? | null | 扫描区域高度 |
| cutOutBottomOffset | double | 0 | 扫描区域底部偏移 |
使用示例:
QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.green,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: 250,
),
)
5.2 自定义矩形扫描框
QrScannerOverlayShape(
borderColor: Colors.blue,
borderWidth: 4,
borderRadius: 8,
borderLength: 25,
cutOutWidth: 300,
cutOutHeight: 150,
overlayColor: Colors.black.withOpacity(0.7),
)
六、OpenHarmony 平台实现原理
6.1 原生 API 映射
qr_code_scanner 在 OpenHarmony 平台上使用以下原生模块:
| Flutter API | OpenHarmony API |
|---|---|
| QRView | PlatformView + XComponent |
| flipCamera | camera.CameraManager + CameraInput |
| toggleFlash | camera.FlashMode / TorchMode |
| pauseCamera | PhotoSession.stop() |
| resumeCamera | PhotoSession.start() |
| 扫码解码 | @kit.ScanKit detectBarcode.decodeImage |
6.2 相机初始化实现
OpenHarmony 使用 CameraKit 初始化相机:
async initCamera(XComponentSurfaceId: string, front: boolean = false): Promise<void> {
const cameraManager = camera.getCameraManager(this.context);
this.camerasDevices = cameraManager.getSupportedCameras();
// 选择前置或后置摄像头
this.curcamerasDevice = front ? this.camerasDevices[1] : this.camerasDevices[0];
// 创建预览输出
let previewProfilesObj = this.getPreviewProfilesObj();
let previewOutput = cameraManager.createPreviewOutput(
previewProfilesObj,
XComponentSurfaceId
);
// 创建相机输入
this.cameraInput = cameraManager.createCameraInput(this.curcamerasDevice);
await this.cameraInput.open();
// 创建拍照会话
this.photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO);
this.photoSession.beginConfig();
this.photoSession.addInput(this.cameraInput);
this.photoSession.addOutput(previewOutput);
await this.photoSession.commitConfig();
await this.photoSession.start();
}
6.3 扫码解码实现
OpenHarmony 使用 ScanKit 的 detectBarcode 进行解码:
decodeImageBuffer(imgComponent: image.Component, width: number, height: number,
channel: MethodChannel, barcodeFormats: Array<number>) {
let byteImg: detectBarcode.ByteImage = {
byteBuffer: imgComponent.byteBuffer,
width: width,
height: height,
format: detectBarcode.ImageFormat.NV21
};
let options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: false,
enableAlbum: false
};
detectBarcode.decodeImage(byteImg, options).then((result) => {
if (result.scanResults.length) {
const res = result.scanResults[0];
const codes = new Map<string, string | object>();
codes.set('code', res.originalValue);
codes.set('type', ConstCodeFormat.BarcodeFormatToString(res.scanType));
channel.invokeMethod('onRecognizeQR', codes);
}
});
}
6.4 闪光灯控制实现
setFlashMode(isOpen: boolean): void {
if (!this.hasFlash()) {
return;
}
const newMode = isOpen
? camera.FlashMode.FLASH_MODE_ALWAYS_OPEN
: camera.FlashMode.FLASH_MODE_CLOSE;
if (!this.isFlashModeSupported(newMode)) {
return;
}
this.photoSession.setFlashMode(newMode);
}
6.5 PlatformView 实现
OhosView 使用 XComponent 显示相机预览:
@Entry
@Component
export struct OhosView {
@Prop params: Params;
@Prop channel: MethodChannel | null = null;
@State surfaceId: string = '';
private mXComponentController: XComponentController = new XComponentController();
build() {
Stack() {
if (this.userGrant) {
Column() {
XComponent({
id: 'componentId',
type: XComponentType.SURFACE,
controller: this.mXComponentController
})
.onLoad(() => {
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
this.scan();
})
.width(this.cameraWidth)
.height(this.cameraHeight)
}
}
}
}
}
七、MethodChannel 通信协议
7.1 方法列表
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
| startScan | List barcodeFormats | null | 开始扫描 |
| getCameraInfo | - | int | 获取摄像头信息 |
| flipCamera | - | int | 切换摄像头 |
| getFlashInfo | - | bool | 获取闪光灯状态 |
| toggleFlash | - | bool? | 切换闪光灯 |
| pauseCamera | - | null | 暂停相机 |
| resumeCamera | - | bool | 恢复相机 |
| stopCamera | - | null | 停止相机 |
| getSystemFeatures | - | Map | 获取设备功能 |
| changeScanArea | scanAreaWidth, scanAreaHeight | null | 设置扫描区域 |
7.2 扫描结果回调
{
"code": "https://example.com",
"type": "QR_CODE",
"rawBytes": [...]
}
7.3 设备功能返回格式
{
"hasFrontCamera": true,
"hasBackCamera": true,
"hasFlash": true,
"activeCamera": "0"
}
八、实战案例
8.1 完整扫码应用示例

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner_ohos/qr_code_scanner_ohos.dart';
void main() {
runApp(const MaterialApp(home: MyHome()));
}
class MyHome extends StatelessWidget {
const MyHome({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('二维码扫描示例')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const QRScannerPage()),
);
},
child: const Text('打开扫描器'),
),
),
);
}
}
class QRScannerPage extends StatefulWidget {
const QRScannerPage({super.key});
State<StatefulWidget> createState() => _QRScannerPageState();
}
class _QRScannerPageState extends State<QRScannerPage> with WidgetsBindingObserver {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
Barcode? result;
QRViewController? controller;
bool isFlashOn = false;
CameraFacing cameraFacing = CameraFacing.back;
Barcode? lastResult;
DateTime? lastScanTime;
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
void reassemble() {
super.reassemble();
if (Platform.isAndroid || defaultTargetPlatform == TargetPlatform.ohos) {
controller?.pauseCamera();
} else if (Platform.isIOS) {
controller?.resumeCamera();
}
}
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.paused:
controller?.pauseCamera();
break;
case AppLifecycleState.resumed:
controller?.resumeCamera();
break;
default:
break;
}
}
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: Container(
color: Colors.grey[200],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (result != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'类型: ${describeEnum(result!.format)}\n数据: ${result!.code}',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
)
else
const Text('请扫描二维码'),
_buildControlButtons(),
],
),
),
),
],
),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 300.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
formatsAllowed: const [
BarcodeFormat.qrcode,
BarcodeFormat.ean13,
BarcodeFormat.code128,
],
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
final now = DateTime.now();
if (lastResult?.code != scanData.code ||
lastScanTime == null ||
now.difference(lastScanTime!).inSeconds > 2) {
setState(() {
result = scanData;
lastResult = scanData;
lastScanTime = now;
});
}
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('相机权限未授予')),
);
}
}
Widget _buildControlButtons() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton.icon(
onPressed: () async {
await controller?.toggleFlash();
final flashStatus = await controller?.getFlashStatus();
setState(() {
isFlashOn = flashStatus ?? false;
});
},
icon: Icon(isFlashOn ? Icons.flash_on : Icons.flash_off),
label: Text(isFlashOn ? '关闭闪光灯' : '打开闪光灯'),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton.icon(
onPressed: () async {
await controller?.flipCamera();
final cameraInfo = await controller?.getCameraInfo();
setState(() {
cameraFacing = cameraInfo ?? CameraFacing.back;
});
},
icon: const Icon(Icons.flip_camera_android),
label: Text(cameraFacing == CameraFacing.back ? '前置摄像头' : '后置摄像头'),
),
),
],
);
}
void dispose() {
controller?.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
代码要点说明:
| 功能模块 | 实现方式 |
|---|---|
| 实时扫描 | 通过 scannedDataStream 监听扫描结果 |
| 闪光灯控制 | toggleFlash() 切换,getFlashStatus() 获取状态 |
| 摄像头切换 | flipCamera() 切换前后摄像头 |
| 权限处理 | onPermissionSet 回调处理权限结果 |
| 生命周期管理 | WidgetsBindingObserver 监听应用状态变化 |
| 扫描格式限制 | formatsAllowed 参数限制支持的码制 |
| 防重复扫描 | 时间间隔判断,2秒内相同结果不重复触发 |
| 热重载处理 | reassemble() 方法处理热重载时的相机状态 |
九、常见问题与解决方案
9.1 相机权限问题
问题:应用启动后无法显示相机预览。
解决方案:
- 确保
module.json5中已正确配置相机权限 - 检查权限说明字符串是否已添加
- 使用
onPermissionSet回调处理权限结果:
QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
onPermissionSet: (controller, hasPermission) {
if (!hasPermission) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请授予相机权限')),
);
}
},
)
9.2 热重载问题
问题:热重载后相机无法正常工作。
解决方案:
void reassemble() {
super.reassemble();
if (Platform.isAndroid || defaultTargetPlatform == TargetPlatform.ohos) {
controller?.pauseCamera();
} else if (Platform.isIOS) {
controller?.resumeCamera();
}
}
9.3 扫描速度慢
问题:扫描识别速度较慢。
解决方案:
- 限制扫描的码制格式,减少解码计算量:
QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
formatsAllowed: const [BarcodeFormat.qrcode], // 仅扫描 QR Code
)
- 调整扫描区域大小,聚焦关键区域
9.4 弱光环境下扫描困难
问题:在光线不足的环境下难以扫描。
解决方案:
Future<void> _enableFlashForLowLight() async {
final flashStatus = await controller?.getFlashStatus();
if (flashStatus == false) {
await controller?.toggleFlash();
setState(() {
isFlashOn = true;
});
}
}
9.5 扫描结果重复触发
问题:扫描成功后结果多次触发。
解决方案:
Barcode? lastResult;
DateTime? lastScanTime;
void _onQRViewCreated(QRViewController controller) {
controller.scannedDataStream.listen((scanData) {
final now = DateTime.now();
if (lastResult?.code != scanData.code ||
lastScanTime == null ||
now.difference(lastScanTime!).inSeconds > 2) {
setState(() {
result = scanData;
lastResult = scanData;
lastScanTime = now;
});
}
});
}
十、与 mobile_scanner 对比
| 特性 | qr_code_scanner | mobile_scanner |
|---|---|---|
| 维护状态 | 维护模式 | 活跃开发 |
| 底层框架 | zxing / MTBBarcode | ML Kit / Vision |
| 性能 | 良好 | 优秀 |
| API 复杂度 | 简单 | 中等 |
| 自定义能力 | 中等 | 高 |
| OpenHarmony 支持 | ✅ | ✅ |
| 从图片扫描 | ❌ | ✅ |
| 多码同时识别 | ❌ | ✅ |
建议:
- 新项目推荐使用
mobile_scanner - 已有项目迁移可继续使用
qr_code_scanner - 需要从图片扫描二维码的场景选择
mobile_scanner
十一、总结
qr_code_scanner_ohos 为 Flutter for OpenHarmony 提供了完整的二维码扫描能力,支持实时扫描、闪光灯控制、摄像头切换等功能。虽然原库已进入维护模式,但对于需要迁移的现有项目仍然是一个可靠的选择。
核心要点:
- 使用 Platform View 架构嵌入原生相机视图
- 通过 QRViewController 控制扫描行为
- 通过 scannedDataStream 监听扫描结果
- 正确处理权限和生命周期
- 可根据需求选择限制码制格式
最佳实践:
- 始终检查并请求相机权限
- 正确管理控制器生命周期
- 提供友好的用户引导和错误提示
- 考虑弱光环境下的用户体验
- 新项目建议评估 mobile_scanner 作为替代方案
更多推荐


所有评论(0)