Scan Kit 默认界面扫码5分钟集成为什么需要默认界面扫码HarmonyOS 开发里集成扫码能力是个高频需求。很多人第一次接触时会尝试自己用 Camera Kit 写一个扫码界面结果发现要处理相机预览、焦距控制、码识别、取景框绘制、闪光灯切换……一整套下来至少要多花两三天而且稳定性还得自己扛。Scan Kit 提供的默认界面模式就是来解决这个问题的——你只需要调一个 API系统就把完整的扫码界面展示给你。用户扫到码后结果通过回调返回。整个过程不超过 5 行核心代码。这个方案适合的场景非常明确快速验证原型阶段或者功能优先级不高先跑通再说功能完整即可不需要定制 UI 样式比如扫描框颜色、提示文案用系统默认的够用码制简单常规的 QR 码、条形码不需要支持过于冷门的码制如果你需要自定义扫描框样式、手动控制相机对焦、或集成扫码结果到某个复杂的业务流程中那就需要走自定义界面模式。但那是另一篇文章的内容。下表可以快速判断该选哪个模式评估维度默认界面模式自定义界面模式集成速度5-10分钟2-3天UI定制能力极低完全控制相机控制系统接管开发者管理码制扩展基础码制全面支持稳定性系统保障依赖开发者代码质量环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机含华为机型核心实现1. 添加依赖并申请权限打开entry/oh-package.json5添加 Scan Kit 依赖{ dependencies: { kit.ScanKit: file:./oh_modules/kit/ScanKit } }然后在module.json5中声明相机权限和扫码服务{ module: { requestPermissions: [ { name: ohos.permission.CAMERA, reason: $string:permission_reason_camera, usedScene: { abilities: [EntryAbility] } } ] } }注意相机权限属于敏感权限运行时系统会自动弹窗提醒用户授权。不需要手动调用requestPermissionsFromUser但reason字段必须填写否则上架应用时会审核失败。2. 编写一个完整的扫码页面创建一个新的页面文件ScanPage.ets集成 Scan Kit 的默认扫码界面// ScanPage.etsimport{scan}fromkit.ScanKit;import{BusinessError}fromkit.BasicServicesKit;EntryComponentstruct ScanPage{// 用于接收扫码结果的回调StatescanResult:string;// 控制扫码界面的显示与隐藏privateisBack:booleanfalse;build(){Column(){// 默认扫码界面的调用方式Scan().startScan({scanOptions:{scanType:[scan.ScanType.QR_CODE,scan.ScanType.BARCODE],// 支持QR码和条形码enableMultiMode:false,// 关闭连续扫码扫到一个码就返回hintText:将二维码/条码放入框内// 提示文字},onResult:(result:scan.ScanResult){// 扫描结果回调this.scanResultresult.originalValue;// 处理结果后可以退出扫码界面this.isBacktrue;},onError:(error:BusinessError){console.error(Scan error: JSON.stringify(error));}}).width(100%).height(100%);// 显示扫描结果if(this.scanResult!){Text(扫描结果: this.scanResult).fontSize(16).margin({top:20}).padding(10);}// 返回按钮Button(返回).onClick((){this.isBacktrue;}).margin({top:20});}.width(100%).height(100%).padding(10);}}关键点解释scan.ScanType.QR_CODE和scan.ScanType.BARCODE是常用的两种码制可以按需添加例如AZTEC、DATA_MATRIX等enableMultiMode: false关闭连续扫码模式。如果设置为true用户扫完一个码后界面不会退出可以继续扫码适合批量录入场景hintText扫描框下方的提示文字可以用来说明扫码用途onResult回调中的result.originalValue就是解码后的字符串内容但是这段代码在实际运行中有一个比较隐蔽的问题后面踩坑部分会详细分析。3. 从主页导航到扫码页在主页面Index.ets中添加一个跳转到扫码页的按钮// Index.etsimport{router}fromkit.ArkUI;EntryComponentstruct Index{build(){Column(){Button(打开扫码).width(80%).height(48).margin({top:200}).onClick((){router.pushUrl({url:pages/ScanPage});});}.width(100%).height(100%);}}到这里一个完整的扫码功能就集成完了。但别急着测试下面这两个坑很可能会让你卡住。常见问题 1扫码界面无法正常的生命周期现象扫码页面正常弹出也能看到系统相机预览但扫码框不显示或者相机启动后马上闪退。更常见的是扫到码后「返回」按钮失效页面无法正常回退。原因这个问题的本质是Scan()组件的生命周期与State变量之间的同步问题。State isBack虽然看起来控制着是否「返回」但Scan()组件并不是一个普通的 UI 组件它内部持有了相机预览、解码引擎等系统级资源。当isBack被设置为true时系统并不会自动释放Scan()实例导致相机资源还在占用页面看起来卡在扫码界面。解决方案使用条件渲染 生命周期回调来彻底销毁Scan()实例。// ScanPage.ets (修正版)import{scan}fromkit.ScanKit;import{BusinessError}fromkit.BasicServicesKit;EntryComponentstruct ScanPage{StatescanResult:string;StateshowScanner:booleantrue;// 控制扫码组件的显示与销毁build(){Column(){if(this.showScanner){Scan().startScan({scanOptions:{scanType:[scan.ScanType.QR_CODE,scan.ScanType.BARCODE],enableMultiMode:false,hintText:将二维码/条码放入框内},onResult:(result:scan.ScanResult){this.scanResultresult.originalValue;// 关闭扫码组件this.showScannerfalse;},onError:(error:BusinessError){console.error(Scan error: JSON.stringify(error));}}).width(100%).height(100%);}// 扫描结果和返回按钮if(this.scanResult!){Text(扫描结果: this.scanResult).fontSize(16).margin({top:20}).padding(10);Button(返回).onClick((){// 使用系统路由返回this.showScannerfalse;router.back();}).margin({top:20});}}.width(100%).height(100%).padding(10);}}关键改进点用if (this.showScanner)包裹Scan()当设置this.showScanner false时条件渲染会从组件树中移除Scan()系统再释放相机资源。这时候再去调用router.back()才能正常返回。常见问题 2连续扫码模式下的结果混乱现象设置enableMultiMode: true后每次扫码都会触发onResult回调但如果用户连续快速扫码scanResult状态会被后一次结果覆盖导致前面扫到的码丢失。原因State是单向数据流不能直接作为累积容器用。连续扫码时回调是异步的多次this.scanResult result.originalValue会相互覆盖最终只保留最后一个值。解决方案使用State数组来存储扫描结果。// ScanPage.ets (连续扫码版本)EntryComponentstruct ScanMultiPage{StatescanResults:string[][];// 使用数组存储多个结果StateshowScanner:booleantrue;build(){Column(){if(this.showScanner){Scan().startScan({scanOptions:{scanType:[scan.ScanType.QR_CODE,scan.ScanType.BARCODE],enableMultiMode:true,// 开启连续扫码hintText:支持连续扫码},onResult:(result:scan.ScanResult){// 追加到数组末尾this.scanResults.push(result.originalValue);},onError:(error:BusinessError){console.error(Scan error: JSON.stringify(error));}}).width(100%).height(100%);}// 展示扫码历史if(this.scanResults.length0){List(){ForEach(this.scanResults,(item:string){ListItem(){Text(item).fontSize(14).padding({left:10,right:10,top:5,bottom:5});}},(item:string)item);}.width(100%).height(200).margin({top:20});Button(返回).onClick((){this.showScannerfalse;router.back();}).margin({top:20});}}.width(100%).height(100%).padding(10);}}注意ForEach的键值生成函数直接使用item本身作为 key因为字符串是唯一的。如果扫描结果可能重复建议使用index作为 key。最佳实践默认界面模式下不要调整Scan()组件的布局参数比如margin、padding、scale等。Scan()内部有自己的相机预览区域外部布局变化会影响预览区域的裁剪导致相机黑边或显示不全。只需要设置width和height为全屏即可。扫码结果拿到后不要立即做复杂操作比如网络请求、数据库写入、弹窗。onResult回调是在子线程中执行的直接在这个回调里修改 UI 状态虽然 ArkUI 能自动同步到主线程但如果处理逻辑超过 10ms会造成 UI 卡顿。建议只做状态赋值如设置this.scanResult真正业务处理放在组件渲染后的onPageShow或aboutToAppear里或者用setTimeout延迟一帧处理。真机测试优先不要在模拟器上调试扫码功能。模拟器没有真实的摄像头硬件Scan()组件在模拟器上会直接报错返回onError。这不是代码的问题是开发环境的限制。很多人在模拟器上跑通了权限逻辑但扫码一直报错其实换个真机就正常了。FAQQ扫码界面出现了但是相机预览是黑的没有画面A最常见的原因是权限申请时机不对。Scan()组件会在创建时立即尝试打开相机如果那时系统权限弹窗还没被用户授权相机就启动失败了。建议在进入扫码页前先通过security.privacyManager检查相机权限状态如果没有授权先引导用户去设置中开启。另一个常见原因是Scan()背景区域太小或父容器设置了overlay遮盖。Q为什么扫 QR 码正常扫条形码就不识别AScanOptions.scanType中需要显式添加BARCODE类型默认只包含QR_CODE。如果只扫 QR 码可以保持不变如果需要扫条形码一定要像示例代码那样同时添加scan.ScanType.BARCODE。另外条形码对排版有要求尽量让条码水平放入扫描框中央区域。Q连续扫码模式下为什么扫了第一个之后就回调不触发了AenableMultiMode: true开启后Scan()组件内部会在每次扫码后重置解码状态但这个重置过程需要 500ms-1s 左右。如果用户在这段时间内连续扫码第二次扫码可能不会被识别。这是系统解码引擎的缓存机制无法通过配置缩短。实际项目中建议在每次扫码回调后播放一个短提示音或震动给用户一个视觉/触觉反馈告知可以扫下一个了。