HarmonyOS鸿蒙Next应用开发实现自定义扫码,请解释业务流程和扫码集成步骤

HarmonyOS鸿蒙Next应用开发实现自定义扫码,请解释业务流程和扫码集成步骤 鸿蒙应用开发使用实现自定义扫码,请解释业务流程和扫码集成步骤

3 回复

一、结论

鸿蒙官方提供了ScanKit来实现自定义扫码的功能诉求。但是对于扫码业务的讲解缺失,通过扫码业务路程,串连官方Kit的接口。可以更深刻的理解自定义扫码业务。

官方Scan Kit接口说明

(1)鸿蒙提供的ScanKit具备以下五种能力:

  1. 扫码直达
  2. 自定义扫码,图像识码 (自定义扫码需要这两种能力组合在一起,所以我分类在一起)
  3. 码图生成
  4. 系统提供的默认界面扫码

(2)业内市面上的自定义扫码界面,主要由以下几个部分功能点构成:

  1. 扫码(单,多)【鸿蒙最多支持四个二维码的识别】
  2. 解析图片二维码
  3. 扫码动画
  4. 扫码振动和音效
  5. 无网络监测与提示
  6. 多码暂停选中点的绘制
  7. 扫码结果根据类型分开处理(应用内部处理,外部H5处理)【这个不做展开】
  8. 焦距控制(放大缩小)

cke_1281.png

二、代码实现和详细解释

首先我们需要绘制整体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));
    }
  }

完成以上步骤后,就可以使用自定义扫码功能,进行二维码和条码的识别了。

三、示例源码:

cke_3688.png

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自定义扫码业务流程

通过相机预览捕获图像,调用系统扫码服务或第三方库解析二维码/条形码数据,处理解析结果。

集成步骤

  1. 申请相机权限(ohos.permission.CAMERA)
  2. 创建相机预览界面
  3. 调用@ohos.zbar或系统Scan Kit接口
  4. 实现扫码结果回调处理
  5. 释放相机资源

需使用ArkTS开发,调用鸿蒙原生API实现扫码功能。

在HarmonyOS Next中实现自定义扫码功能,主要业务流程和集成步骤如下:

一、业务流程

  1. 权限申请:应用启动时,需动态申请相机权限(ohos.permission.CAMERA)。
  2. 相机初始化:创建并配置相机实例,设置预览输出流。
  3. 扫码分析:通过图像分析能力,持续捕获预览帧并进行二维码/条形码解析。
  4. 结果处理:识别成功后,解析数据并执行后续业务逻辑(如跳转链接、商品查询等)。
  5. 资源释放:退出页面时及时释放相机及相关资源。

二、关键集成步骤

  1. 添加权限与依赖

    • module.json5中声明相机权限:
      "requestPermissions": [
        {
          "name": "ohos.permission.CAMERA"
        }
      ]
      
    • build-profile.json5中引入图像处理依赖(如需要本地算法库)。
  2. 相机预览配置

    • 使用CameraManager获取相机设备列表。
    • 通过CameraInputPreviewOutput等构建相机流水线,将预览画面输出至XComponent组件。
  3. 扫码识别实现

    • 方案一:使用系统ScanKit服务(推荐)
      • 集成@ohos/scan Kit,调用scan.createScanner()创建扫描器。
      • 通过scan.scan()或绑定预览流实时识别。
    • 方案二:自定义图像分析
      • 创建ImageAnalyzer,在onAnalyze回调中获取图像数据。
      • 使用第三方库(如ZXing)或AI模型解析二维码。
  4. 界面与交互

    • 通过XComponent显示相机预览。
    • 叠加自定义扫描框、动画提示等UI元素。
    • 监听返回键、识别结果回调,控制业务流程。

三、注意事项

  • 需适配不同设备的相机兼容性。
  • 实时识别时注意性能优化,避免高频分析导致卡顿。
  • 若使用系统ScanKit,需确保设备已预置该服务。

以上流程可实现从权限控制、相机调用到扫码识别的完整功能。根据业务复杂度,可选择系统ScanKit快速集成,或自定义分析实现更灵活的控制。

回到顶部