基础回顾与进阶目标
在之前的文章里,我们完成了自定义扫码界面的基础搭建,跑通了“扫码→解析→展示结果”的闭环。但把这个基础版本放到真实项目里,通常会发现几个问题:
- 不支持同时识别多个码:产品要同时扫一批条码,基础版只能返回一个。
- 功耗控制不够精细:默认配置下CPU跑得比较猛,手机容易发烫。
- 识别速度不稳定:二维码瞬间识别,但条形码需要对准很久。
- 交互体验单一:不支持手势缩放和对焦,用户只能靠移动手机来调整。
这篇实战教程会逐一解决这些问题。最终实现的自定义扫码组件,会具备多码同时识别、按场景降低功耗、混合码制快速识别、以及双击对焦+滑动缩放的能力。
它解决什么问题
先看一个对比,明确自定义扫码(进阶版)的优势:
| 能力维度 | 默认界面扫码 | 基础自定义扫码 | 进阶自定义扫码 |
|---|---|---|---|
| 多码识别 | ❌ 单码 | ❌ 单码 | ✅ 多码 |
| 功耗策略 | 固定 | 固定 | ✅ 可配置 |
| 混合码制 | ✅ 支持 | ✅ 支持 | ✅ 支持+可优化 |
| 手势交互 | ❌ 无 | ❌ 无 | ✅ 缩放/对焦 |
这个进阶配置适合需要快速扫描、大批量扫描、或者对功耗有严格要求的场景。比如库存盘点、物流分拣、学生签到、会议室扫码入场等。
环境说明
DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 目标设备:手机 / 平板核心实现
1. 多码同时识别
基础版的扫码配置会在scanBarcode方法里设置multiRecognize: false。多码识别只需要修改这一个参数。
// MultiCodeScanner.etsimport{scanBarcode,ScanOptions,ScanResult}from'@kit.ScanKit'exportclassMultiCodeScanner{privatescanOptions:ScanOptionsconstructor(){this.scanOptions={// 开启多码识别multiRecognize:true,// 支持的码制:一般建议只开启用到的,提高性能formatList:[scanBarcode.ScanFormat.QR_CODE,scanBarcode.ScanFormat.CODE_128,scanBarcode.ScanFormat.EAN_13]}}startScan(surface:image.PixelMap):Promise<ScanResult[]>{returnnewPromise((resolve,reject)=>{scanBarcode.scanBarcode(surface,this.scanOptions,(err,results)=>{if(err){reject(err)return}// 注意:results 是个数组,每个元素对应一个识别到的码resolve(results)})})}}注意事项:
- 开启
multiRecognize: true后,即使画面里只有一个码,返回的也是长度为1的数组。业务逻辑不能假设数组长度。 formatList非常重要。多码识别意味着要跟多种码的解析逻辑打交道。如果不需要,一定不要填进去。否则内部会尝试更多的图像分析,耗时和功耗都会增加。
2. 降低功耗与提高识别速度
功耗和速度往往是矛盾的。ScanOptions里可以调整三个关键参数:
resolution:识别的分辨率scanInterval:两次识别之间的间隔(毫秒)frameScan:是否逐帧识别
实际项目里,需要根据不同场景来权衡。
// 低功耗配置:静态场景(比如对准一个二维码付款)constlowPowerOptions:ScanOptions={multiRecognize:false,resolution:{width:960,height:720},// 降低分辨率scanInterval:500,// 500ms扫一次formatList:[scanBarcode.ScanFormat.QR_CODE]}// 高性能配置:动态场景(比如抓拍快速移动的条码)consthighPerformanceOptions:ScanOptions={multiRecognize:true,resolution:{width:1280,height:960},// 提高分辨率scanInterval:100,// 100ms扫一次formatList:[scanBarcode.ScanFormat.QR_CODE,scanBarcode.ScanFormat.CODE_128,scanBarcode.ScanFormat.EAN_13]}场景化切换示例:
// 在页面State中管理配置@StatecurrentConfig:ScanOptions=lowPowerOptions// 根据用户动作切换togglePerformance(high:boolean){if(high){this.currentConfig=highPerformanceOptions}else{this.currentConfig=lowPowerOptions}}这里的切换时机也可以根据识别结果自动做。比如识别连续失败3次,自动降分辨率、加快频率。识别成功后降下来,省电。
3. 混合模式配置
混合模式指的是同时支持二维码和条形码。formatList已经能覆盖,但这里有一个隐藏技巧:不要先在Preview里把画面降到太小的分辨率,再去识别混合码。
条形码需要对比相邻线条的宽度比例,分辨率太低时,边缘会模糊,识别率下降。二维码是矩阵解析,对分辨率的敏感度反而低。
建议配置:
- 二维码为主:
resolution可以到960*720以下 - 条形码为主:
resolution不低于1280*960 - 混合模式:
resolution保持1280*960,再通过scanInterval来平衡功耗。
4. 手势缩放与对焦
这是用户体感最明显的交互改进。使用TapGesture实现双击对焦,使用PanGesture实现滑动变焦。
// CameraWithGestures.ets@Componentexportstruct CameraWithGestures{// 缩放比例,0-1之间,系统会自动映射到设备光学变焦范围@StatezoomRatio:number=0.5privatelastZoomRatio:number=0.5privatescanSurface:ScanSurface// 实际项目中会用Surface组件build(){Stack(){// 相机预览SurfaceScanSurface().width('100%').height('100%').gesture(GestureGroup(GestureMode.Exclusive,// 双击对焦TapGesture({count:2}).onAction((event:GestureEvent)=>{if(event&&this.scanSurface){// 获取双击坐标letx=event.fingerList[0].globalXlety=event.fingerList[0].globalY// 调用相机对焦APIthis.scanSurface.setFocusPoint(x,y)}}),// 滑动变焦PanGesture().onActionStart(()=>{this.lastZoomRatio=this.zoomRatio}).onActionUpdate((event:GestureEvent)=>{// 根据水平或垂直滑动距离计算新的zoomRatioletdelta=event.offsetY/500// 常数校准系数letnewZoom=Math.max(0,Math.min(1,this.lastZoomRatio+delta))this.zoomRatio=newZoomif(this.scanSurface){this.scanSurface.setZoom(this.zoomRatio)}})))}}}关键点:
- 缩放和对焦是独立API,
setZoom控制整体缩放,setFocusPoint控制对焦区域。 - 对焦的坐标需要是预览区域的坐标系,一般取归一化坐标值。
- 滑动灵敏度的常数(如500)需要根据实际表现微调。不同设备摄像头焦距不同。
常见的踩坑记录
坑1:多码识别返回结果顺序不稳定
现象:同一个画面,多次扫码,ScanResult数组里码的顺序可能不一致。如果业务依赖位置顺序(比如把第一个码作为主码),会出现异常。
原因:多码识别内部是并行或异步执行的,哪个先识别出来,就先放到数组里。不是按照画面中的位置顺序。
解法:不要依赖数组索引。在每个ScanResult的结果对象里,会有x、y坐标信息。根据坐标排序来确定主码。
// 按左上角坐标排序results.sort((a,b)=>{// 先按y轴排序,再按x轴if(a.y!==b.y){returna.y-b.y}returna.x-b.x})坑2:降低分辨率后条形码识别率严重下降
现象:配置为960*720后,二维码识别正常,但条形码基本上识别不出来。
原因:条形码对图像清晰度要求高。分辨率降低后,条形码边缘毛刺增多,导致码的模块宽度测量不准。
解法:如果应用中条形码占比大,建议混合模式时不要降低分辨率。如果能判断当前码制(比如通过界面按钮切换“条形码模式”),那么只在条形码模式下改用高分辨率,其他时候降分辨率省电。
坑3:双击对焦在部分设备上不灵敏
现象:双击后画面没有明显的自动对焦动作。
原因:部分设备的setFocusPoint需要先在相机配置中开启ContinuousAutoFocus(连续自动对焦)才能被触发。还有一种可能是触摸坐标转换不正确。
解法:在初始化相机时,确保开启了连续自动对焦。坐标需要归一化到[0,1]范围。
this.scanSurface.setFocusPoint(x/this.scanSurface.width,y/this.scanSurface.height)最佳实践
按业务场景动态调整扫描配置:不要在应用启动时一次设置好再也不改。比如用户进入“批量扫描”界面,用高分辨率+多码识别;用户进入“付款码”界面,用低分辨率+单码识别。这样可以兼顾体验和功耗。
只开启真正用到的码制:每多一种码制,内部会增加一个解析模型。如果不限制,API会默认开启所有支持的码制,识别速度会明显下降。如果业务只需要二维码,
formatList只填QR_CODE,能提高30%-50%的识别速度。不要在build()中频繁创建ScanOptions对象:
ScanOptions是一个普通对象,如果写在build()中,每次渲染时都会生成新实例,导致下游的对比逻辑失效。建议在@State或@Observed中管理,或者用LazyLoading初始化一次。手势缩放要跟相机初始化状态同步:如果相机还在初始化中,
setZoom会失败。建议在相机的onReady事件之后再设置缩放手势有效。
FAQ
Q:官方文档里也写了multiRecognize,但我的代码里配置了为什么一直只返回一个码?
A:确认两点:一是multiRecognize是否设置成了true,二是formatList里是否包含了需要的码制。如果formatList为空,多码识别不会生效。还有一个可能,就是画面里确实只有一个码,那返回数组长度就是1,这是正常的。
Q:滑动缩放感觉不流畅,有什么判断基准吗?
A:先确认setZoom调用是否频繁。如果每次onActionUpdate都调用,频率太高可能会卡顿。建议做一下节流,每50ms内只触发一次。另外检查zoomRatio计算是否正确,不同设备的变焦范围差异较大,0-1只是通用映射,部分设备在两端会无响应。
Q:低功耗配置下,识别速度慢了很多,怎么优化?
A:低功耗配置里scanInterval设成了500ms,意味着每500ms才尝试一次识别。如果用户需要连续识别,这确实不够快。可以折中:在识别到码后立即降低频率,识别失败时再提高频率。或者用frameScan逐帧识别但降低分辨率。核心还是场景适应性调整。
如果你也遇到了扫码场景下其他奇怪的问题,建议从这三条入手检查:分辨率、码制列表、间隔时长。这三个参数组合出来的效果差异巨大。