HarmonyOS鸿蒙Next应用开发实现自定义扫码,请解释业务流程和扫码集成步骤
HarmonyOS鸿蒙Next应用开发实现自定义扫码,请解释业务流程和扫码集成步骤 鸿蒙应用开发使用实现自定义扫码,请解释业务流程和扫码集成步骤
一、结论
鸿蒙官方提供了ScanKit来实现自定义扫码的功能诉求。但是对于扫码业务的讲解缺失,通过扫码业务路程,串连官方Kit的接口。可以更深刻的理解自定义扫码业务。
(1)鸿蒙提供的ScanKit具备以下五种能力:
- 扫码直达
- 自定义扫码,图像识码 (自定义扫码需要这两种能力组合在一起,所以我分类在一起)
- 码图生成
- 系统提供的默认界面扫码
(2)业内市面上的自定义扫码界面,主要由以下几个部分功能点构成:
- 扫码(单,多)【鸿蒙最多支持四个二维码的识别】
- 解析图片二维码
- 扫码动画
- 扫码振动和音效
- 无网络监测与提示
- 多码暂停选中点的绘制
- 扫码结果根据类型分开处理(应用内部处理,外部H5处理)【这个不做展开】
- 焦距控制(放大缩小)

二、代码实现和详细解释
首先我们需要绘制整体UI界面布局,常规分为相机流容器view,动画表现view,按钮控制区view。
1.创建相机视频流容器 在ScanKit中相机流通过XComponent组件作为相机流的容器。
[@Builder](/user/Builder) ScanKitView(){
XComponent({
id: 'componentId',
type: XComponentType.SURFACE,
controller: this.mXComponentController
})
.onLoad(async () => {
// 视频流开始加载回调
})
.width(this.cameraWidth) // cameraWidth cameraHeight 参见步骤二
.height(this.cameraHeight)
}
2.需要测算XComponent呈现的相机流宽高
// 竖屏时获取屏幕尺寸,设置预览流全屏示例
setDisplay() {
// 折叠屏无 or 折叠
if(display.getFoldStatus() == display.FoldStatus.FOLD_STATUS_UNKNOWN || display.getFoldStatus() == display.FoldStatus.FOLD_STATUS_FOLDED){
// 默认竖屏
let displayClass = display.getDefaultDisplaySync();
this.displayHeight = px2vp(displayClass.height);
this.displayWidth = px2vp(displayClass.width);
}else{
// 折叠屏展开 or 半展开
let displayClass = display.getDefaultDisplaySync();
let tempHeight = px2vp(displayClass.height);
let tempWidth = px2vp(displayClass.width);
console.info("debugDisplay", 'tempHeight: ' + tempHeight + " tempWidth: " + tempWidth);
this.displayHeight = tempHeight + px2vp(8);
this.displayWidth = ( tempWidth - px2vp(64) ) / 2;
}
console.info("debugDisplay", 'final displayHeight: ' + this.displayHeight + " displayWidth: " + this.displayWidth);
let maxLen: number = Math.max(this.displayWidth, this.displayHeight);
let minLen: number = Math.min(this.displayWidth, this.displayHeight);
const RATIO: number = 16 / 9;
this.cameraHeight = maxLen;
this.cameraWidth = maxLen / RATIO;
this.cameraOffsetX = (minLen - this.cameraWidth) / 2;
}
3.使用相机,需要用户同意申请的Camera权限
module.json5配置
"requestPermissions": [
{
"name" : "ohos.permission.CAMERA",
"reason": "$string:app_name",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when":"inuse"
}
}
]
需要注意时序,每次显示自定义扫码界面,都需要检查权限。所有建议放在onPageshow系统周期内。
async onPageShow() {
await this.requestCameraPermission();
}
/**
* 用户申请相机权限
*/
async requestCameraPermission() {
let grantStatus = await this.reqPermissionsFromUser();
for (let i = 0; i < grantStatus.length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
console.log(this.TAG, "Succeeded in getting permissions.");
this.userGrant = true;
}
}
}
/**
* 用户申请权限
* [@returns](/user/returns)
*/
async reqPermissionsFromUser(): Promise<number[]> {
let context = getContext() as common.UIAbilityContext;
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus = await atManager.requestPermissionsFromUser(context, ['ohos.permission.CAMERA']);
return grantStatus.authResults;
}
4. 配置初始化扫码相机
import { customScan } from '[@kit](/user/kit).ScanKit'
private setScanConfig(){
// 多码扫码识别,enableMultiMode: true 单码扫码识别enableMultiMode: false
let options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true,
enableAlbum: true
}
}
// 初始化接口
customScan.init(options);
5.开启相机 此处需要注意时序,开启相机需要在权限检查后,配置初始化了相机,并且在XComponent相机视频流容器加载回调后进行。(如果需要配置闪光灯的处理,可在此处一同处理)【完整代码示例,参见章节三】
[@Builder](/user/Builder) ScanKitView(){
XComponent({
id: 'componentId',
type: XComponentType.SURFACE,
controller: this.mXComponentController
})
.onLoad(async () => {
// 获取XComponent组件的surfaceId
this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
console.info(this.TAG, "Succeeded in getting surfaceId: " + this.surfaceId);
this.startCamera();
this.setFlashLighting();
})
.width(this.cameraWidth)
.height(this.cameraHeight)
.position({ x: this.cameraOffsetX, y: this.cameraOffsetY })
}
/**
* 启动相机
*/
private startCamera() {
this.isShowBack = false;
this.scanResult = [];
let viewControl: customScan.ViewControl = {
width: this.cameraWidth,
height: this.cameraHeight,
surfaceId : this.surfaceId
};
// 自定义启动第四步,请求扫码接口,通过Promise方式回调
try {
customScan.start(viewControl)
.then(async (result: Array<scanBarcode.ScanResult>) => {
console.error(this.TAG, 'result: ' + JSON.stringify(result));
if (result.length) {
// 解析码值结果跳转应用服务页
this.scanResult = result;
this.isShowBack = true;
// 获取到扫描结果后暂停相机流
try {
customScan.stop().then(() => {
console.info(this.TAG, 'Succeeded in stopping scan by promise ');
}).catch((error: BusinessError) => {
console.error(this.TAG, 'Failed to stop scan by promise err: ' + JSON.stringify(error));
});
} catch (error) {
console.error(this.TAG, 'customScan.stop err: ' + JSON.stringify(error));
}
}
}).catch((error: BusinessError) => {
console.error(this.TAG, 'customScan.start err: ' + JSON.stringify(error));
});
} catch (err) {
console.error(this.TAG, 'customScan.start err: ' + JSON.stringify(err));
}
}
完成以上步骤后,就可以使用自定义扫码功能,进行二维码和条码的识别了。
三、示例源码:

ScanPage.ets 兼容折叠屏,Navigation。
import { customScan, scanBarcode, scanCore } from '[@kit](/user/kit).ScanKit'
import { hilog } from '[@kit](/user/kit).PerformanceAnalysisKit'
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit'
import { abilityAccessCtrl, common } from '[@kit](/user/kit).AbilityKit'
import { display, promptAction, router } from '[@kit](/user/kit).ArkUI'
[@Builder](/user/Builder)
export function ScanPageBuilder(name: string, param: object){
if(isLog(name, param)){
ScanPage()
}
}
function isLog(name: string, param: object){
console.log("ScanPageBuilder", " ScanPageBuilder init name: " + name);
return true;
}
[@Entry](/user/Entry)
[@Component](/user/Component)
export struct ScanPage {
private TAG: string = '[customScanPage]';
[@State](/user/State) userGrant: boolean = false // 是否已申请相机权限
[@State](/user/State) surfaceId: string = '' // xComponent组件生成id
[@State](/user/State) isShowBack: boolean = false // 是否已经返回扫码结果
[@State](/user/State) isFlashLightEnable: boolean = false // 是否开启了闪光灯
[@State](/user/State) isSensorLight: boolean = false // 记录当前环境亮暗状态
[@State](/user/State) cameraHeight: number = 480 // 设置预览流高度,默认单位:vp
[@State](/user/State) cameraWidth: number = 300 // 设置预览流宽度,默认单位:vp
[@State](/user/State) cameraOffsetX: number = 0 // 设置预览流x轴方向偏移量,默认单位:vp
[@State](/user/State) cameraOffsetY: number = 0 // 设置预览流y轴方向偏移量,默认单位:vp
[@State](/user/State) zoomValue: number = 1 // 预览流缩放比例
[@State](/user/State) setZoomValue: number = 1 // 已设置的预览流缩放比例
[@State](/user/State) scaleValue: number = 1 // 屏幕缩放比
[@State](/user/State) pinchValue: number = 1 // 双指缩放比例
[@State](/user/State) displayHeight: number = 0 // 屏幕高度,单位vp
[@State](/user/State) displayWidth: number = 0 // 屏幕宽度,单位vp
[@State](/user/State) scanResult: Array<scanBarcode.ScanResult> = [] // 扫码结果
private mXComponentController: XComponentController = new XComponentController()
async onPageShow() {
// 自定义启动第一步,用户申请权限
await this.requestCameraPermission();
// 自定义启动第二步:设置预览流布局尺寸
this.setDisplay();
// 自定义启动第三步,配置初始化接口
this.setScanConfig();
}
private setScanConfig(){
// 多码扫码识别,enableMultiMode: true 单码扫码识别enableMultiMode: false
let options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true,
enableAlbum: true
}
customScan.init(options);
}
async onPageHide() {
// 页面消失或隐藏时,停止并释放相机流
this.userGrant = false;
this.isFlashLightEnable = false;
this.isSensorLight = false;
try {
customScan.off('lightingFlash');
} catch (error) {
hilog.error(0x0001, this.TAG, `Failed to off lightingFlash. Code: ${error.code}, message: ${error.message}`);
}
await customScan.stop();
// 自定义相机流释放接口
customScan.release().then(() => {
hilog.info(0x0001, this.TAG, 'Succeeded in releasing customScan by promise.');
}).catch((error: BusinessError) => {
hilog.error(0x0001, this.TAG,
`Failed to release customScan by promise. Code: ${error.code}, message: ${error.message}`);
})
}
/**
* 用户申请权限
* [@returns](/user/returns)
*/
async reqPermissionsFromUser(): Promise<number[]> {
hilog.info(0x0001, this.TAG, 'reqPermissionsFromUser start');
let context = getContext() as common.UIAbilityContext;
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus = await atManager.requestPermissionsFromUser(context, ['ohos.permission.CAMERA']);
return grantStatus.authResults;
}
/**
* 用户申请相机权限
*/
async requestCameraPermission() {
let grantStatus = await this.reqPermissionsFromUser();
for (let i = 0; i < grantStatus.length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
console.log(this.TAG, "Succeeded in getting permissions.");
this.userGrant = true;
}
}
}
// 竖屏时获取屏幕尺寸,设置预览流全屏示例
setDisplay() {
// 折叠屏无 or 折叠
if(display.getFoldStatus() == display.FoldStatus.FOLD_STATUS_UNKNOWN || display.getFoldStatus() == display.FoldStatus.FOLD_STATUS_FOLDED){
// 默认竖屏
let displayClass = display.getDefaultDisplaySync();
this.displayHeight = px2vp(displayClass.height);
this.displayWidth = px2vp(displayClass.width);
}else{
// 折叠屏展开 or 半展开
let displayClass = display.getDefaultDisplaySync();
let tempHeight = px2vp(displayClass.height);
let tempWidth = px2vp(displayClass.width);
console.info("debugDisplay", 'tempHeight: ' + tempHeight + " tempWidth: " + tempWidth);
this.displayHeight = tempHeight + px2vp(8);
this.displayWidth = ( tempWidth - px2vp(64) ) / 2;
}
console.info("debugDisplay", 'final displayHeight: ' + this.displayHeight + " displayWidth: " + this.displayWidth);
let maxLen: number = Math.max(this.displayWidth, this.displayHeight);
let minLen: number = Math.min(this.displayWidth, this.displayHeight);
const RATIO: number = 16 / 9;
this.cameraHeight = maxLen;
this.cameraWidth = maxLen / RATIO;
this.cameraOffsetX = (minLen - this.cameraWidth) / 2;
}
// toast显示扫码结果
async showScanResult(result: scanBarcode.ScanResult) {
// 使用toast显示出扫码结果
promptAction.showToast({
message: JSON.stringify(result),
duration: 5000
});
}
/**
* 启动相机
*/
private startCamera() {
this.isShowBack = false;
this.scanResult = [];
let viewControl: customScan.ViewControl = {
width: this.cameraWidth,
height: this.cameraHeight,
surfaceId : this.surfaceId
};
// 自定义启动第四步,请求扫码接口,通过Promise方式回调
try {
customScan.start(viewControl)
.then(async (result: Array<scanBarcode.ScanResult>) => {
console.error(this.TAG, 'result: ' + JSON.stringify(result));
if (result.length) {
// 解析码值结果跳转应用服务页
this.scanResult = result;
this.isShowBack = true;
// 获取到扫描结果后暂停相机流
try {
customScan.stop().then(() => {
console.info(this.TAG, 'Succeeded in stopping scan by promise ');
}).catch((error: BusinessError) => {
console.error(this.TAG, 'Failed to stop scan by promise err: ' + JSON.stringify(error));
});
} catch (error) {
console.error(this.TAG, 'customScan.stop err: ' + JSON.stringify(error));
}
}
}).catch((error: BusinessError) => {
console.error(this.TAG, 'customScan.start err: ' + JSON.stringify(error));
});
} catch (err) {
console.error(this.TAG, 'customScan.start err: ' + JSON.stringify(err));
}
}
/**
* 注册闪光灯监听接口
*/
private setFlashLighting(){
customScan.on('lightingFlash', (error, isLightingFlash) => {
if (error) {
console.info(this.TAG, "customScan lightingFlash error: " + JSON.stringify(error));
return;
}
if (isLightingFlash) {
this.isFlashLightEnable = true;
} else {
if (!customScan?.getFlashLightStatus()) {
this.isFlashLightEnable = false;
}
}
this.isSensorLight = isLightingFlash;
});
}
// 自定义扫码界面的顶部返回按钮和扫码提示
[@Builder](/user/Builder)
TopTool() {
Column() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
Text('返回')
.onClick(async () => {
// router.back();
this.mNavContext?.pathStack.removeByName("ScanPage");
})
}.padding({ left: 24, right: 24, top: 40 })
Column() {
Text('扫描二维码/条形码')
Text('对准二维码/条形码,即可自动扫描')
}.margin({ left: 24, right: 24, top: 24 })
}
.height(146)
.width('100%')
}
[@Builder](/user/Builder) ScanKitView(){
XComponent({
id: 'componentId',
type: XComponentType.SURFACE,更多关于HarmonyOS鸿蒙Next应用开发实现自定义扫码,请解释业务流程和扫码集成步骤的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
鸿蒙Next自定义扫码业务流程
通过相机预览捕获图像,调用系统扫码服务或第三方库解析二维码/条形码数据,处理解析结果。
集成步骤
- 申请相机权限(ohos.permission.CAMERA)
- 创建相机预览界面
- 调用@ohos.zbar或系统Scan Kit接口
- 实现扫码结果回调处理
- 释放相机资源
需使用ArkTS开发,调用鸿蒙原生API实现扫码功能。
在HarmonyOS Next中实现自定义扫码功能,主要业务流程和集成步骤如下:
一、业务流程
- 权限申请:应用启动时,需动态申请相机权限(
ohos.permission.CAMERA)。 - 相机初始化:创建并配置相机实例,设置预览输出流。
- 扫码分析:通过图像分析能力,持续捕获预览帧并进行二维码/条形码解析。
- 结果处理:识别成功后,解析数据并执行后续业务逻辑(如跳转链接、商品查询等)。
- 资源释放:退出页面时及时释放相机及相关资源。
二、关键集成步骤
-
添加权限与依赖
- 在
module.json5中声明相机权限:"requestPermissions": [ { "name": "ohos.permission.CAMERA" } ] - 在
build-profile.json5中引入图像处理依赖(如需要本地算法库)。
- 在
-
相机预览配置
- 使用
CameraManager获取相机设备列表。 - 通过
CameraInput、PreviewOutput等构建相机流水线,将预览画面输出至XComponent组件。
- 使用
-
扫码识别实现
- 方案一:使用系统
ScanKit服务(推荐)- 集成
@ohos/scanKit,调用scan.createScanner()创建扫描器。 - 通过
scan.scan()或绑定预览流实时识别。
- 集成
- 方案二:自定义图像分析
- 创建
ImageAnalyzer,在onAnalyze回调中获取图像数据。 - 使用第三方库(如ZXing)或AI模型解析二维码。
- 创建
- 方案一:使用系统
-
界面与交互
- 通过
XComponent显示相机预览。 - 叠加自定义扫描框、动画提示等UI元素。
- 监听返回键、识别结果回调,控制业务流程。
- 通过
三、注意事项
- 需适配不同设备的相机兼容性。
- 实时识别时注意性能优化,避免高频分析导致卡顿。
- 若使用系统ScanKit,需确保设备已预置该服务。
以上流程可实现从权限控制、相机调用到扫码识别的完整功能。根据业务复杂度,可选择系统ScanKit快速集成,或自定义分析实现更灵活的控制。

