HarmonyOS NEXT ScanKit 自定义扫码完全指南
在 HarmonyOS NEXT 应用开发中扫码功能几乎是各类 App 的标配——支付、登录、信息录入处处都需要它。鸿蒙 ScanKit 提供了从“一行代码搞掂”到“完全自己画 UI”的完整能力栈。本文将聚焦自定义界面扫码深入拆解业务流程、核心 API 调用逻辑、UI 自定义技巧和常见坑点帮助你在应用中实现风格统一、体验流畅的扫码功能。一、ScanKit 能力分层明确“自定义扫码”的定位在动手之前先搞清楚 ScanKit 的能力分层这会直接影响你的技术选型层级能力适用场景权限要求系统级扫码直达系统相机/控制中心扫码直接跳转 App 指定页面希望从系统入口直达 App 服务无需应用内申请相机权限默认界面扫码调起系统统一扫码界面返回结果通用扫码场景对 UI 无定制需求系统预授权无需应用申请相机权限自定义界面扫码开发者完全自建扫码 UI控制相机流需要 UI 与 App 风格统一、加入交互特效、多码选择等深度定制需在 module.json5 中声明相机权限并动态申请图片解析/码图生成从相册图片识别二维码或将文本生成二维码“识别图中二维码”、离线生成码图存储权限、无相机权限要求本文聚焦第三层——自定义界面扫码适合需要完全掌控扫码界面的场景。二、自定义扫码的核心业务流程自定义扫码的本质是开发者自己提供相机预览界面通过 ScanKit 的customScan模块将相机流数据喂给识码引擎引擎回调返回识别结果。整体流程如下text用户进入扫码页 ↓ 动态申请相机权限ohos.permission.CAMERA ↓ 初始化 XComponent渲染相机预览流 ↓ 调用 customScan.start() 启动识别引擎传入 Surface ID ↓ 引擎持续分析帧数据通过 Callback 返回识别结果 ↓ 收到结果 → 业务处理 → 暂停/释放相机资源关键点在于相机预览由 XComponent 组件负责扫码引擎由customScan模块控制两者通过 Surface ID 关联。三、前置准备权限与依赖3.1 声明相机权限在entry/src/main/module.json5中添加权限声明json{ module: { requestPermissions: [ { name: ohos.permission.CAMERA, reason: $string:permission_camera_reason, usedScene: { abilities: [EntryAbility], when: inuse } } ] } }同时需要在resources/base/element/string.json中配置权限使用说明否则应用市场审核会打回。3.2 动态申请权限ScanKit 自定义扫码不会像默认扫码那样预授权必须由应用主动申请tsimport { abilityAccessCtrl, common } from kit.AbilityKit; import { BusinessError } from kit.BasicServicesKit; async function requestCameraPermission(context: common.UIAbilityContext): Promiseboolean { const atManager abilityAccessCtrl.createAtManager(); const grantStatus await atManager.requestPermissionsFromUser(context, [ohos.permission.CAMERA]); return grantStatus.authResults[0] abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED; }3.3 导入所需模块tsimport { customScan, scanBarcode, scanCore } from kit.ScanKit; import { BusinessError } from kit.BasicServicesKit; import { hilog } from kit.PerformanceAnalysisKit;四、自定义扫码开发实战4.1 XComponent相机预览的“画布”自定义扫码的 UI 核心是XComponent组件它负责承载相机预览流。需要指定type surface并获取其surfaceId传给扫码引擎。tsComponent struct CustomScanPage { State surfaceId: string ; private xComponentController: XComponentController new XComponentController(); build() { Stack() { // 相机预览区域 XComponent({ id: scan_xcomponent, type: XComponentType.SURFACE, controller: this.xComponentController }) .onLoad((context) { this.surfaceId context.getSurfaceId(); // Surface 加载完成后启动扫码引擎 this.startCustomScan(); }) .width(100%) .height(100%) // 在此之上叠加自定义 UI 元素扫描框、提示文字、闪光灯按钮等 this.buildCustomUI(); } } }4.2 启动自定义扫码引擎在获取到surfaceId后调用customScan.start将相机流与识码引擎绑定tsimport { customScan, scanBarcode, scanCore } from kit.ScanKit; import { BusinessError } from kit.BasicServicesKit; function startCustomScan(surfaceId: string): void { const viewControl: customScan.ViewControl { surfaceId: surfaceId, // 从 XComponent 获取 width: 1080, // 预览宽度需与 XComponent 实际尺寸匹配 height: 1920 // 预览高度 }; try { customScan.start(viewControl, (err: BusinessError, data: ArrayscanBarcode.ScanResult) { if (err) { hilog.error(0x0001, CustomScan, 扫码启动失败: ${err.code}, ${err.message}); return; } if (data data.length 0) { // 处理扫码结果 const result data[0]; hilog.info(0x0001, CustomScan, 识别结果: ${result.originalValue}, 类型: ${result.scanType}); // 跳转到结果页或执行业务逻辑 handleScanResult(result); } }); } catch (error) { const e error as BusinessError; hilog.error(0x0001, CustomScan, customScan.start 异常: ${e.message}); } }4.3 控制扫码引擎的生命周期自定义扫码需要手动管理引擎状态避免资源泄露ts// 暂停识别如用户切换到后台 customScan.pause(); // 恢复识别 customScan.resume(); // 重新扫码当识别到的码不在扫描框内时可调用此方法继续扫描 customScan.rescan(); // 完全释放资源页面销毁时必须调用 customScan.release();典型调用时机aboutToDisappear()中调用release()扫码成功后调用pause()待用户确认后再resume()或release()应用切后台时调用pause()切回前台时resume()五、自定义 UI 的核心技巧5.1 绘制扫码框与识别区域限定很多场景要求“只识别扫描框内的码”。customScan返回的结果中包含scanCodeRect码图最小外接矩形坐标开发者需要自行判断该矩形是否位于扫描框内。ts// 假设扫码框位于屏幕中央尺寸 800x800 px const scanBox { left: (cameraWidth - 800) / 2, top: (cameraHeight - 800) / 2, right: (cameraWidth 800) / 2, bottom: (cameraHeight 800) / 2 }; // 在 customScan.start 的 Callback 中判断 if (data data.length 0) { const rect data[0].scanCodeRect; if (rect) { const isInBox rect.left scanBox.left rect.top scanBox.top rect.right scanBox.right rect.bottom scanBox.bottom; if (isInBox) { // 码在框内处理结果 handleResult(data[0]); } else { // 码不在框内继续扫描 customScan.rescan(); } } }注意scanCodeRect的坐标系与XComponent的尺寸单位需保持一致px 或 vp否则判断会失准。5.2 闪光灯控制自定义扫码需要自己实现闪光灯按钮。通过customScan模块获取和设置闪光灯状态ts// 获取当前闪光灯状态 const isOn customScan.isFlashlightOn(); // 开启/关闭闪光灯 customScan.setFlashlight(true); // true开false关也可以监听环境光传感器在暗光条件下自动显示闪光灯按钮ts// 通过 customScan.isSensorLight 判断是否暗光 if (customScan.isSensorLight) { // 显示闪光灯按钮 }5.3 调整闪光灯按钮位置从官方示例代码中可以看出闪光灯按钮的位置可通过margin自由调整ts// 原示例中将闪光灯放在右下角 FlashLight({ isLargeScreen: false }) .margin({ bottom: -100, left: 350 }) .visibility((this.scanService.isFlashlight || this.scanService.isSensorLight) this.scanLayout.isFlashShow ? Visibility.Visible : Visibility.Hidden)5.4 变焦控制扫码引擎支持动态调整变焦比适合远距离扫码场景ts// 获取当前变焦比 const zoom customScan.getZoom(); // 设置变焦比通常为 1.0 ~ 3.0 之间 customScan.setZoom(2.0);六、从相册选图识别自定义扫码场景中往往需要支持“从相册选择二维码图片”作为补充。此时使用scanBarcode.decode接口tsimport { scanBarcode, scanCore } from kit.ScanKit; import { image } from kit.ImageKit; async function decodeFromAlbum(pixelMap: image.PixelMap): Promisestring { const options: scanBarcode.ScanOptions { scanTypes: [scanCore.ScanType.ALL] // 支持所有码类型 }; try { const results await scanBarcode.decode(pixelMap, options); if (results results.length 0) { return results[0].originalValue; } return ; } catch (error) { const e error as BusinessError; hilog.error(0x0001, Decode, 图片识别失败: ${e.message}); return ; } }需要配合photoAccessHelper选择图片将选中的图片转换为PixelMap后传入。七、扫码直达系统级无缝跳转虽然本文聚焦自定义扫码但“扫码直达”是 ScanKit 6.1 引入的重要能力值得了解。配置后可让用户通过系统相机或控制中心扫码直接跳转到 App 指定页面。配置步骤在module.json5的skills中声明urisjson{ skills: [{ actions: [action.view], uris: [{ scheme: https, host: www.yourapp.com, path: /product }] }] }在EntryAbility的onCreate和onNewWant中解析want.uri提取参数并跳转对应页面。这样用户扫到https://www.yourapp.com/product?id12345的二维码时系统会直接拉起你的 App 并跳转到商品详情页。八、常见问题与避坑指南Q1自定义扫码时相机无法打开检查三点module.json5是否声明了ohos.permission.CAMERA运行时是否动态申请了权限并得到用户授权XComponent的surfaceId是否在onLoad回调中正确获取并传入customScan.startQ2扫码结果一直返回空或识别率低检查scanTypes配置是否正确如只识 QR_CODE 却去扫条形码调整customScan.start中的width/height参数使其与实际预览尺寸匹配适当调整对焦距离或调用setZoom优化画面清晰度Q3如何实现“只识别扫描框内的码”参考第五章 5.1 节的坐标判断逻辑通过scanCodeRect与自定义扫描框的位置比对不在框内的码调用customScan.rescan()忽略。Q4页面销毁后仍然收到扫码回调务必在aboutToDisappear()中调用customScan.release()释放引擎资源避免内存泄漏和回调残留。九、总结HarmonyOS NEXT 的 ScanKit 通过customScan模块为开发者提供了完整的自定义扫码能力。核心思路是用 XComponent 渲染相机预览获取surfaceId调用customScan.start绑定引擎在 Callback 中处理识别结果通过scanCodeRect实现“框内识别”精准控制扫码范围手动管理引擎生命周期pause/resume/release优化资源使用叠加自定义 UI 元素实现与 App 风格一致的扫码界面