鸿蒙(API 12 Beta3版)【自定义界面扫码】

news2024/11/16 22:54:45

基本概念

自定义界面扫码能力提供了相机流控制接口,可根据自身需求自定义扫码界面,适用于对扫码界面有定制化需求的应用开发。

说明

通过自定义页面扫码可以实现应用内的扫码功能,为了应用更好的体验,推荐同时[接入“扫码直达”服务],应用可以同时支持系统扫码入口(控制中心扫一扫)和应用内扫码两种方式跳转到指定服务页面。

场景介绍

自定义界面扫码能力提供扫码相机流控制接口,支持相机流的初始化、开启、暂停、释放、重新扫码功能;支持闪光灯的状态获取、开启、关闭;支持变焦比的获取和设置;支持设置相机焦点和连续自动对焦;支持对条形码、二维码、MULTIFUNCTIONAL CODE进行扫码识别(具体类型参见[ScanType]),并获得码类型、码值、码位置信息、相机预览流(YUV)。该能力可用于单码和多码的扫描识别。

开发者集成自定义界面扫码能力可以自行定义扫码的界面样式,请按照业务流程完成扫码接口调用实现实时扫码功能。建议开发者基于[Sample Code]做个性化修改。

说明

YUV(相机预览流图像数据)适合于扫码和识物的综合识别场景,开发者需要自己控制相机流,普通扫码场景无需关注。

约束与限制

  • 需要请求相机的使用权限。
  • 需要开发者自行实现扫码的人机交互界面。例如:多码场景需要暂停相机流由用户选择一个码图进行识别。

业务流程

1

  1. 发起请求: 用户向开发者的应用发起扫码请求,应用拉起已定义好的扫码界面。

  2. 申请授权: 应用需要向用户申请相机权限授权。若未同意授权,则无法使用此功能。

  3. 启动自定义界面扫码: 在扫码前必须调用init接口初始化自定义扫码界面,加载资源。相机流初始化结束后,调用start接口开始扫码。

  4. 自定义界面扫码相机操作: 可以配置自定义界面扫码相机操作参数,调整相应功能,包括闪光灯、变焦、焦距、暂停、重启扫码等。例如:

    • 根据当前码图位置,比如当前码图太远或太近时,调用getZoom获取变焦比,setZoom接口设置变焦比,调整焦距以便于用户扫码。
    • 根据当前扫码的光线条件或根据on(‘lightingFlash’)监听闪光灯开启时机,通过getFlashLightStatus接口先获取闪光灯状态,再调用openFlashLight/closeFlashLight接口控制闪光灯开启或关闭,以便于用户进行扫码。
    • 调用setFocusPoint设置对焦位置,resetFocus恢复默认对焦模式,以便于用户进行扫码。
    • 在应用处于前后台或其他特殊场景需要中断/重新进行扫码时,可调用stop或start接口来控制相机流达到暂停或重新扫码的目的。
  5. 自定义界面扫码: Scan Kit API在扫码完成后会返回扫码结果。同时根据开发者的需要,Scan Kit API会返回每帧相机预览流数据。如需不重启相机并重新触发一次扫码,可以在start接口的Callback异步回调中,调用rescan接口。完成扫码后,需调用release接口进行释放扫码资源的操作。

  6. 获取结果: 解析码值结果跳转应用服务页。

接口说明

自定义界面扫码提供init、start、stop、release、getFlashLightStatus、openFlashLight、closeFlashLight、setZoom、getZoom、setFocusPoint、resetFocus、rescan、on(‘lightingFlash’)、off(‘lightingFlash’)接口,其中部分接口返回值有两种返回形式:Callback和Promise回调。Callback和Promise回调函数只是返回值方式不一样,功能相同。

接口名描述
[init](options?: scanBarcode.[ScanOptions]): void初始化自定义界面扫码,加载资源。无返回结果。
[start](viewControl: [ViewControl]): Promise<Array<scanBarcode.[ScanResult]>>启动扫码相机流。使用Promise异步回调获取扫码结果。
stop: Promise暂停扫码相机流。使用Promise异步回调返回执行结果。
release: Promise释放扫码相机流。使用Promise异步回调返回执行结果。
[start](viewControl: ViewControl, callback: AsyncCallback<Array<scanBarcode.ScanResult>>, frameCallback?: AsyncCallback<[ScanFrame]>): void启动扫码相机流。使用Callback异步回调返回扫码结果以及YUV图像数据。
getFlashLightStatus: boolean获取闪光灯状态。返回结果为布尔值,true为打开状态,false为关闭状态。
openFlashLight: void开启闪光灯。无返回结果。
closeFlashLight: void关闭闪光灯。无返回结果。
[setZoom](zoomValue : number): void设置变焦比。无返回结果。
getZoom: number获取当前的变焦比。
[setFocusPoint](point: scanBarcode.[Point]): void设置相机焦点。
resetFocus: void设置连续自动对焦模式。
rescan: void触发一次重新扫码。仅对start接口Callback异步回调有效,Promise异步回调无效。
[stop](callback: AsyncCallback): void暂停扫码相机流。使用Callback异步回调返回执行结果。
[release](callback: AsyncCallback): void释放扫码相机流。使用Callback异步回调返回执行结果。
[on](type: ‘lightingFlash’, callback: AsyncCallback): void注册闪光灯打开时机回调,使用Callback异步回调返回闪光灯打开时机。
[off](type: ‘lightingFlash’, callback?: AsyncCallback): void注销闪光灯打开时机回调,使用Callback异步回调返回注销结果。

开发步骤

自定义界面扫码接口支持自定义UI界面,识别相机流中的条形码,二维码以及MULTIFUNCTIONAL CODE,并返回码图的值、类型、码的位置信息(码图最小外接矩形左上角和右下角的坐标)以及相机预览流(YUV)。

以下示例为调用自定义界面扫码接口拉起相机流并返回扫码结果和相机预览流(YUV)。

  1. 在开发应用前,需要先申请相机相关权限,确保应用拥有访问相机的权限。在“module.json5”文件中配置相机权限,具体配置方式

    权限名说明授权方式
    ohos.permission.CAMERA允许应用使用相机扫码。user_grant
  2. 使用接口[requestPermissionsFromUser]请求用户授权。具体申请方式及校验方式。

  3. 导入自定义界面扫码接口以及相关接口模块,导入方法如下。

import { scanCore, scanBarcode, customScan } from '@kit.ScanKit';
// 导入功能涉及的权限申请、回调接口
import { router, promptAction, display } from '@kit.ArkUI';
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common, abilityAccessCtrl } from '@kit.AbilityKit';
  1. 遵循[业务流程]完成自定义界面扫码功能。

    说明

    1. 在设置start接口的viewControl参数时,width和height与[XComponent]的宽高值相同,start接口会根据XComponent的宽高比例从相机的分辨率选择最优分辨率,如果比例与相机的分辨率比例相差过大会返回内部错误。当前支持的分辨率比例为16:9、4:3、1:1。竖屏场景下,XComponent的高度需要大于宽度,且高宽比在支持的分辨率比例中。横屏场景下,XComponent的宽度需要大于高度,且宽高比在支持的分辨率比例中。
    2. XComponent的宽高需根据使用场景计算适配。例如:在开发设备为折叠屏时,需按照折叠屏的展开态和折叠态分别计算XComponent的宽高,start接口会根据XComponent的宽高适配对应的相机分辨率。设备屏幕宽高可通过[display.getDefaultDisplaySync]方法获取(获取的为px单位,需要通过[px2vp]。
    • 通过Promise方式回调,调用自定义界面扫码接口拉起相机流并返回扫码结果。
@Entry
@Component
struct CustomScanPage {
  @State userGrant: boolean = false // 是否已申请相机权限
  @State surfaceId: string = '' // xComponent组件生成id
  @State isShowBack: boolean = false // 是否已经返回扫码结果
  @State isFlashLightEnable: boolean = false // 是否开启了闪光灯
  @State isSensorLight: boolean = false // 记录当前环境亮暗状态
  @State cameraHeight: number = 640 // 设置预览流高度,默认单位:vp
  @State cameraWidth: number = 360 // 设置预览流宽度,默认单位:vp
  @State cameraOffsetX: number = 0 // 设置预览流x轴方向偏移量,默认单位:vp
  @State cameraOffsetY: number = 0 // 设置预览流y轴方向偏移量,默认单位:vp
  @State zoomValue: number = 1 // 预览流缩放比例
  @State setZoomValue: number = 1 // 已设置的预览流缩放比例
  @State scaleValue: number = 1 // 屏幕缩放比
  @State pinchValue: number = 1 // 双指缩放比例
  @State displayHeight: number = 0 // 屏幕高度,单位vp
  @State displayWidth: number = 0 // 屏幕宽度,单位vp
  @State scanResult: Array<scanBarcode.ScanResult> = [] // 扫码结果
  private mXComponentController: XComponentController = new XComponentController()
  private TAG: string = '[customScanPage]'

  async onPageShow() {
    // 自定义启动第一步,用户申请权限
    await this.requestCameraPermission();
    // 多码扫码识别,enableMultiMode: true 单码扫码识别enableMultiMode: false
    let options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL],
      enableMultiMode: true,
      enableAlbum: true
    }
    // 自定义启动第二步:设置预览流布局尺寸
    this.setDisplay();
    // 自定义启动第三步,初始化接口
    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}`);
    })
  }

  // 用户申请权限
  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) {
        // 用户授权,可以继续访问目标操作
        hilog.info(0x0001, this.TAG, 'Succeeded in getting permissions.');
        this.userGrant = true;
      }
    }
  }

  // 竖屏时获取屏幕尺寸,设置预览流全屏示例
  setDisplay() {
    // 默认竖屏
    let displayClass = display.getDefaultDisplaySync();
    this.displayHeight = px2vp(displayClass.height);
    this.displayWidth = px2vp(displayClass.width);
    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
    });
  }
  initCamera() {
    this.isShowBack = false;
    this.scanResult = [];
    let viewControl: customScan.ViewControl = {
      width: this.cameraWidth,
      height: this.cameraHeight,
      surfaceId: this.surfaceId
    };
    // 自定义启动第四步,请求扫码接口,通过Promise方式回调
    customScan.start(viewControl)
      .then(async (result: Array<scanBarcode.ScanResult>) => {
        hilog.info(0x0001, this.TAG, `result: ${JSON.stringify(result)}`);
        if (result.length) {
          // 解析码值结果跳转应用服务页
          this.scanResult = result;
          this.isShowBack = true;
          // 获取到扫描结果后暂停相机流
          customScan.stop();
        }
      });
  }

  // 自定义扫码界面的顶部返回按钮和扫码提示
  @Builder
  TopTool() {
    Column() {
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
        Text('返回')
          .onClick(async () => {
            router.back();
          })
      }.padding({ left: 24, right: 24, top: 40 })

      Column() {
        Text('扫描二维码/条形码')
        Text('对准二维码/条形码,即可自动扫描')
      }.margin({ left: 24, right: 24, top: 24 })
    }
    .height(146)
    .width('100%')
  }

  build() {
    Stack() {
      if (this.userGrant) {
        Column() {
          XComponent({
            id: 'componentId',
            type: XComponentType.SURFACE,
            controller: this.mXComponentController
          })
            .onLoad(async () => {
              hilog.info(0x0001, this.TAG, 'Succeeded in loading, onLoad is called.');
              // 获取XComponent组件的surfaceId
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
              hilog.info(0x0001, this.TAG, `Succeeded in getting surfaceId: ${this.surfaceId}`);
              this.initCamera();
              // 闪光灯监听接口
              customScan.on('lightingFlash', (error, isLightingFlash) => {
                if (error) {
                  hilog.error(0x0001, this.TAG,
                    `Failed to on lightingFlash. Code: ${error.code}, message: ${error.message}`);
                  return;
                }
                if (isLightingFlash) {
                  this.isFlashLightEnable = true;
                } else {
                  if (!customScan.getFlashLightStatus()) {
                    this.isFlashLightEnable = false;
                  }
                }
                this.isSensorLight = isLightingFlash;
              });
            })  
            .width(this.cameraWidth)
            .height(this.cameraHeight)
            .position({ x: this.cameraOffsetX, y: this.cameraOffsetY })
        }
        .height('100%')
        .width('100%')
      }

      Column() {
        this.TopTool()
        Column() {
        }
        .layoutWeight(1)
        .width('100%')

        Column() {
          Row() {
            // 闪光灯按钮,启动相机流后才能使用
            Button('FlashLight')
              .onClick(() => {
                // 根据当前闪光灯状态,选择打开或关闭闪关灯
                if (customScan.getFlashLightStatus()) {
                  customScan.closeFlashLight();
                  setTimeout(() => {
                    this.isFlashLightEnable = this.isSensorLight;
                  }, 200);
                } else {
                  customScan.openFlashLight();
                }
              })
              .visibility((this.userGrant && this.isFlashLightEnable) ? Visibility.Visible : Visibility.None)

            // 扫码成功后,点击按钮后重新扫码
            Button('Scan')
              .onClick(() => {
                // 点击按钮重启相机流,重新扫码
                this.initCamera();
              })
              .visibility(this.isShowBack ? Visibility.Visible : Visibility.None)
          }

          Row() {
            // 预览流设置缩放比例
            Button('缩放比例,当前比例:' + this.setZoomValue)
              .onClick(() => {
                // 设置相机缩放比例
                if (!this.isShowBack) {
                  if (!this.zoomValue || this.zoomValue === this.setZoomValue) {
                    this.setZoomValue = customScan.getZoom();
                  } else {
                    this.zoomValue = this.zoomValue;
                    customScan.setZoom(this.zoomValue);
                    setTimeout(() => {
                      if (!this.isShowBack) {
                        this.setZoomValue = customScan.getZoom();
                      }
                    }, 1000);
                  }
                }
              })
          }
          .margin({ top: 10, bottom: 10 })

          Row() {
            // 输入要设置的预览流缩放比例
            TextInput({ placeholder: '输入缩放倍数' })
              .type(InputType.Number)
              .borderWidth(1)
              .backgroundColor(Color.White)
              .onChange(value => {
                this.zoomValue = Number(value);
              })
          }
        }
        .width('50%')
        .height(180)
      }

      // 单码、多码扫描后,显示码图蓝点位置。点击toast码图信息
      ForEach(this.scanResult, (item: scanBarcode.ScanResult, index: number) => {
        if (item.scanCodeRect) {
          Image($rawfile('scan_selected2.svg'))
            .width(40)
            .height(40)
            .markAnchor({ x: 20, y: 20 })
            .position({
              x: (item.scanCodeRect.left + item?.scanCodeRect?.right) / 2 + this.cameraOffsetX,
              y: (item.scanCodeRect.top + item?.scanCodeRect?.bottom) / 2 + this.cameraOffsetY
            })
            .onClick(() => {
              this.showScanResult(item);
            })
        }
      })
    }
    // 建议相机流设置为全屏
    .width('100%')
    .height('100%')
    .onClick((event: ClickEvent) => {
      // 是否已扫描到结果
      if (this.isShowBack) {
        return;
      }
      // 点击屏幕位置,获取点击位置(x,y),设置相机焦点
      let x1 = vp2px(event.displayY) / (this.displayHeight + 0.0);
      let y1 = 1.0 - (vp2px(event.displayX) / (this.displayWidth + 0.0));
      customScan.setFocusPoint({ x: x1, y: y1 });
      hilog.info(0x0001, this.TAG, `Succeeded in setting focusPoint x1: ${x1}, y1: ${y1}`);
      // 设置连续自动对焦模式
      setTimeout(() => {
        customScan.resetFocus();
      }, 200);
    }).gesture(PinchGesture({ fingers: 2 })
      .onActionStart((event: GestureEvent) => {
        hilog.info(0x0001, this.TAG, 'Pinch start');
      })
      .onActionUpdate((event: GestureEvent) => {
        if (event) {
          this.scaleValue = event.scale;
        }
      })
      .onActionEnd((event: GestureEvent) => {
        // 是否已扫描到结果
        if (this.isShowBack) {
          return;
        }
        // 获取双指缩放比例,设置变焦比
        try {
          let zoom = customScan.getZoom();
          this.pinchValue = this.scaleValue * zoom;
          customScan.setZoom(this.pinchValue);
          hilog.info(0x0001, this.TAG, 'Pinch end');
        } catch (error) {
          hilog.error(0x0001, this.TAG, `Failed to setZoom. Code: ${error.code}, message: ${error.message}`);
        }
      }))
  }
}
  • 通过Callback方式回调,调用自定义界面扫码接口拉起相机流并返回扫码结果和相机预览流(YUV)。
import { bundleManager, PermissionRequestResult, Permissions } from '@kit.AbilityKit';

const TAG = '[YUV CPSample]';
let context = getContext(this) as common.UIAbilityContext;

// 用户申请权限
export class PermissionsUtil {
  public static async checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus: abilityAccessCtrl.GrantStatus = -1;
    // 获取应用程序的accessTokenID
    let tokenId: number = 0;
    let bundleInfo: bundleManager.BundleInfo =
      await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
    // 校验应用是否被授予权限
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
    return grantStatus;
  }

  // 申请相机权限
  public static async reqPermissionsFromUser(): Promise<number[]> {
    hilog.info(0x0001, TAG, 'Succeeded in getting permissions by promise.')
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus: PermissionRequestResult = { permissions: [], authResults: [] }
    grantStatus = await atManager.requestPermissionsFromUser(context, ['ohos.permission.CAMERA']);
    return grantStatus.authResults;
  }
}

@Extend(Column)
function mainStyle() {
  .width('100%')
  .height('100%')
  .padding({
    top: 40
  })
  .justifyContent(FlexAlign.Center)
}

@Entry
@Component
struct YUVScan {
  @State userGrant: boolean = false // 是否已申请相机权限
  @State surfaceId: string = '' // xComponent组件生成id
  @State cameraHeight: number = 640 // 设置预览流高度,默认单位:vp
  @State cameraWidth: number = 360 // 设置预览流宽度,默认单位:vp
  @State cameraOffsetX: number = 0 // 设置预览流x轴方向偏移量,默认单位:vp
  @State cameraOffsetY: number = 0 // 设置预览流y轴方向偏移量,默认单位:vp
  @State zoomValue: number = 1 // 预览流缩放比例
  @State setZoomValue: number = 1 // 已设置的预览流缩放比例
  @State isReleaseCamera: boolean = false // 是否已释放相机流
  @State scanWidth: number = 384 // xComponent宽度,默认设置384,单位vp
  @State scanHeight: number = 682 // xComponent高度,默认设置682,单位vp
  @State scanBottom: number = 220
  @State scanOffsetX: number = 0 // xComponent位置x轴偏移量,单位vp
  @State scanOffsetY: number = 0 // xComponent位置y轴偏移量,单位vp
  @State scanCodeRect: Array<scanBarcode.ScanCodeRect> = [] // 扫码结果码图位置
  @State scanFlag: boolean = false // 是否已经扫码到结果
  @State scanFrameResult: string = ''
  @State scaleValue: number = 1 // 屏幕缩放比
  @State pinchValue: number = 1 // 双指缩放比例
  @State displayHeight: number = 0 // 屏幕高度,单位vp
  @State displayWidth: number = 0 // 屏幕宽度,单位vp
  private mXComponentController: XComponentController = new XComponentController()
  private viewControl: customScan.ViewControl = { width: 1920, height: 1080, surfaceId: this.surfaceId }
  options: scanBarcode.ScanOptions = {
    // 扫码类型,可选参数
    scanTypes: [scanCore.ScanType.ALL],
    // 是否开启多码识别,可选参数
    enableMultiMode: true,
    // 是否开启相册扫码,可选参数
    enableAlbum: true,
  }
  // 返回自定义扫描结果的回调
  private callback: AsyncCallback<scanBarcode.ScanResult[]> =
    async (error: BusinessError, result: scanBarcode.ScanResult[]) => {
      if (error && error.code) {
        hilog.error(0x0001, TAG,
          `Failed to get ScanResult by callback. Code: ${error.code}, message: ${error.message}`);
        return;
      }
      // 解析码值结果跳转应用服务页
      hilog.info(0x0001, TAG, `Succeeded in getting ScanResult by callback, result: ${JSON.stringify(result)}`);
    }
  // 返回相机帧的回调
  private frameCallback: AsyncCallback<customScan.ScanFrame> =
    async (error: BusinessError, frameResult: customScan.ScanFrame) => {
      if (error) {
        hilog.error(0x0001, TAG, `Failed to get ScanFrame by callback. Code: ${error.code}, message: ${error.message}`);
        return;
      }
      // byteBuffer相机YUV图像数组
      hilog.info(0x0001, TAG,
        `Succeeded in getting ScanFrame.byteBuffer.byteLength: ${frameResult.byteBuffer.byteLength}`)
      hilog.info(0x0001, TAG, `Succeeded in getting ScanFrame.width: ${frameResult.width}`)
      hilog.info(0x0001, TAG, `Succeeded in getting ScanFrame.height: ${frameResult.height}`)
      this.scanFrameResult = JSON.stringify(frameResult.scanCodeRects);
      let newWidth = frameResult.height;
      if (frameResult && frameResult.scanCodeRects && frameResult.scanCodeRects.length > 0 && !this.scanFlag) {
        if (frameResult.scanCodeRects[0]) {
          this.stopCamera();
          this.scanCodeRect = [];
          this.scanFlag = true;
          // 码图位置信息转换
          this.changeToXComponent(frameResult);
        } else {
          this.scanFlag = false;
        }
      }
    }

  // frameCallback横向码图位置信息转换为预览流xComponent对应码图位置信息
  changeToXComponent(frameResult: customScan.ScanFrame) {
    if (frameResult && frameResult.scanCodeRects) {
      let frameHeight = frameResult.height;
      let ratio = this.scanWidth / frameHeight;
      frameResult.scanCodeRects.forEach((item) => {
        this.scanCodeRect.push({
          left: this.toFixedNumber((frameHeight - item.bottom) * ratio),
          top: this.toFixedNumber(item.left * ratio),
          right: this.toFixedNumber((frameHeight - item.top) * ratio),
          bottom: this.toFixedNumber(item.right * ratio)
        });
      });
      this.scanFrameResult = JSON.stringify(this.scanCodeRect);
    }
  }

  toFixedNumber(no: number): number {
    return Number((no).toFixed(1));
  }

  async onPageShow() {
    // 自定义启动第一步,用户申请权限
    const permissions: Array<Permissions> = ['ohos.permission.CAMERA'];
    // 自定义启动第二步:设置预览流布局尺寸
    this.setDisplay();
    let grantStatus = await PermissionsUtil.checkAccessToken(permissions[0]);
    if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
      // 已经授权,可以继续访问目标操作
      this.userGrant = true;
      if (this.surfaceId) {
        // 自定义启动第三步,初始化接口
        this.initCamera();
      }
    } else {
      // 申请相机权限
      this.requestCameraPermission();
    }
  }

  async onPageHide() {
    this.releaseCamera();
  }

  // 用户申请权限
  async requestCameraPermission() {
    let grantStatus = await PermissionsUtil.reqPermissionsFromUser()
    let length: number = grantStatus.length;
    for (let i = 0; i < length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作
        this.userGrant = true;
      } else {
        // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
        this.userGrant = false;
      }
    }
  }

  // 竖屏时获取屏幕尺寸,设置预览流全屏示例
  setDisplay() {
    // 以手机为例计算宽高
    let displayClass = display.getDefaultDisplaySync();
    this.displayHeight = px2vp(displayClass.height);
    this.displayWidth = px2vp(displayClass.width);
    if (displayClass !== null) {
      this.scanWidth = px2vp(displayClass.width);
      this.scanHeight = Math.round(this.scanWidth * this.viewControl.width / this.viewControl.height);
      this.scanBottom = Math.max(220, px2vp(displayClass.height) - this.scanHeight);
      this.scanOffsetX = 0;
      this.scanOffsetY = 0;
    }
  }

  // 初始化相机流
  async initCamera() {
    this.isReleaseCamera = false;
    customScan.init(this.options);
    hilog.info(0x0001, TAG, 'Succeeded in initing customScan with options.');
    this.scanCodeRect = [];
    this.scanFlag = false;
    // 自定义启动第四步,请求扫码接口
    customScan.start(this.viewControl, this.callback, this.frameCallback);
  }

  // 暂停相机流
  async stopCamera() {
    if (!this.isReleaseCamera) {
      customScan.stop();
    }
  }

  // 释放相机流
  async releaseCamera() {
    if (!this.isReleaseCamera) {
      await this.stopCamera();
      await customScan.release();
      this.isReleaseCamera = true;
    }
  }

  build() {
    Stack() {
      // 相机预览流XComponent
      if (this.userGrant) {
        Column() {
          XComponent({
            id: 'componentId',
            type: XComponentType.SURFACE,
            controller: this.mXComponentController
          })
            .onLoad(() => {
              hilog.info(0x0001, TAG, 'Succeeded in loading, onLoad is called.');
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
              hilog.info(0x0001, TAG, `Succeeded in getting surfaceId is ${this.surfaceId}`);
              this.viewControl = { width: this.scanWidth, height: this.scanHeight, surfaceId: this.surfaceId };
              // 启动相机进行扫码
              this.initCamera();
            })
            .height(this.scanHeight)
            .width(this.scanWidth)
            .position({ x: 0, y: 0 })
        }
        .height('100%')
        .width('100%')
        .position({ x: this.scanOffsetX, y: this.scanOffsetY })
      }

      Column() {
        Column() {
        }
        .layoutWeight(1)
        .width('100%')

        Column() {

          Row() {
            // 闪光灯按钮,启动相机流后才能使用
            Button('FlashLight')
              .onClick(() => {
                // 根据当前闪光灯状态,选择打开或关闭闪关灯
                if (customScan.getFlashLightStatus()) {
                  customScan.closeFlashLight();
                } else {
                  customScan.openFlashLight();
                }
              })
              .visibility(this.scanFlag ? Visibility.None : Visibility.Visible)
          }

          Row() {
            // 预览流设置缩放比例
            Button('缩放比例,当前比例:' + this.setZoomValue)
              .width(200)
              .alignSelf(ItemAlign.Center)
              .onClick(() => {
                // 设置相机缩放比例
                if (!this.scanFlag) {
                  if (!this.zoomValue || this.zoomValue === this.setZoomValue) {
                    this.setZoomValue = customScan.getZoom();
                  } else {
                    this.zoomValue = this.zoomValue;
                    customScan.setZoom(this.zoomValue);
                    setTimeout(() => {
                      if (!this.scanFlag) {
                        this.setZoomValue = customScan.getZoom();
                      }
                    }, 1000);
                  }
                }
              })
          }
          .margin({ top: 10, bottom: 10 })
          .visibility(this.scanFlag ? Visibility.None : Visibility.Visible)

          Row() {
            // 输入要设置的预览流缩放比例
            TextInput({ placeholder: '输入缩放倍数' })
              .width(200)
              .type(InputType.Number)
              .borderWidth(1)
              .backgroundColor(Color.White)
              .onChange(value => {
                this.zoomValue = Number(value);
              })
          }
          .visibility(this.scanFlag ? Visibility.None : Visibility.Visible)

          Text(this.scanFlag ? '继续扫码' : '扫码中')
            .height(30)
            .fontSize(16)
            .fontColor(Color.White)
            .onClick(() => {
              if (this.scanFlag) {
                this.scanFrameResult = '';
                this.initCamera();
              }
            })
          Text('扫码结果:' + this.scanFrameResult).fontColor(Color.White).fontSize(12)
        }
        .width('100%')
        .height(this.scanBottom)
        .backgroundColor(Color.Black)
      }
      .mainStyle()

      Image($rawfile('scan_back.svg'))
        .width(20)
        .height(20)
        .position({
          x: 40,
          y: 40
        })
        .onClick(() => {
          router.back();
        })

      // 实时扫码码图中心点位置
      if (this.scanFlag && this.scanCodeRect.length > 0) {
        ForEach(this.scanCodeRect, (item: scanBarcode.ScanCodeRect, index: number) => {
          Image($rawfile('scan_selected2.svg'))
            .width(40)
            .height(40)
            .markAnchor({ x: 20, y: 20 })
            .position({
              x: (item.left + item.right) / 2 + this.scanOffsetX,
              y: (item.top + item.bottom) / 2 + this.scanOffsetY
            })
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.userGrant ? Color.Transparent : Color.Black)
    .onClick((event: ClickEvent) => {
      // 是否已扫描到结果
      if (this.scanFlag) {
        return;
      }
      // 点击屏幕位置,获取点击位置(x,y),设置相机焦点
      let x1 = vp2px(event.displayY) / (this.displayHeight + 0.0);
      let y1 = 1.0 - (vp2px(event.displayX) / (this.displayWidth + 0.0));
      customScan.setFocusPoint({ x: x1, y: y1 });
      hilog.info(0x0001, TAG, `Succeeded in setting focusPoint x1: ${x1}, y1: ${y1}`);
      setTimeout(() => {
        customScan.resetFocus();

      }, 200);
    })
    .gesture(PinchGesture({ fingers: 2 })
      .onActionStart((event: GestureEvent) => {
        hilog.info(0x0001, TAG, 'Pinch start');
      })
      .onActionUpdate((event: GestureEvent) => {
        if (event) {
          this.scaleValue = event.scale;
        }
      })
      .onActionEnd((event: GestureEvent) => {
        // 是否已扫描到结果
        if (this.scanFlag) {
          return;
        }
        // 获取双指缩放比例,设置变焦比
        try {
          let zoom = customScan.getZoom();
          this.pinchValue = this.scaleValue * zoom;
          customScan.setZoom(this.pinchValue);
          hilog.info(0x0001, TAG, 'Pinch end');
        } catch (error) {
          hilog.error(0x0001, TAG, `Failed to setZoom. Code: ${error.code}, message: ${error.message}`);
        }
      }))
  }
}
  1. 通过scanCodeRect数据可确定码图中心点的位置,使用说明如下。

    • scanCodeRect的四个点坐标如下,可根据坐标点绘制码图外围矩形框。

      • 左上角(x, y):(left, top)
      • 右上角(x, y):(right, top)
      • 左下角(x, y):(left, bottom)
      • 右下角(x, y):(right, bottom)
    • 由于码图中心点坐标需和xComponent的坐标保持一致,如果xComponent的x轴和y轴存在偏移,则码图位置需做相应的偏移。例如:x轴偏移量为:scanOffsetX;y轴偏移量为:scanOffsetY,中心点坐标最终转换为:

      • x = (left + right) / 2 + scanOffsetX
      • y = (top + bottom) / 2 + scanOffsetY

模拟器开发

暂不支持模拟器使用,调用会返回错误信息“Emulator is not supported.”

最后呢

很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

在这里插入图片描述

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细鸿蒙(OpenHarmony )手册(共计1236页)与鸿蒙(OpenHarmony )开发入门视频,帮助大家在技术的道路上更进一步。

  • 《鸿蒙 (OpenHarmony)开发学习视频》
  • 《鸿蒙生态应用开发V2.0白皮书》
  • 《鸿蒙 (OpenHarmony)开发基础到实战手册》
  • OpenHarmony北向、南向开发环境搭建
  • 《鸿蒙开发基础》
  • 《鸿蒙开发进阶》
  • 《鸿蒙开发实战》

在这里插入图片描述

总结

鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿
1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2088194.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

海信电视与《黑神话 :悟空》合作,大屏端流畅游戏体验如何保障?

上线即巅峰 国产3A游戏引发热潮 近期&#xff0c;首款国产3A游戏《黑神话&#xff1a;悟空》自上线以来&#xff0c;便迅速在各大游戏平台占据热销榜首&#xff0c;其销量及在线人数均刷新历史记录。同时&#xff0c;该游戏在各大社交媒体中也拥有超高讨论度&#xff0c;其精湛…

在Ubuntu24.04上安装多主多从的高可用Kubernetes节点

前言 因为镜像拉取失败&#xff0c;所以好多小伙伴说calico网络插件安装失败。之前写过CentOS7上安装多主节点的Kubernets集群&#xff0c;而CentOS7又停止维护了。所以借着在Ubuntu上安装多主的Kubernetes集群的机会&#xff0c;使用国内镜像安装calico网络插件。 视频教程地…

Git实现代码托管

在电脑端下载git:Git - 安装 Git (git-scm.com) 点击继续下载&#xff1a;一直next即可 下载完毕 winR:cmd 输入&#xff1a;git 不报错即可 登录 gitee账号&#xff0c;根据教程完成内容即可创建成功 &#xff01;&#xff01;一定要先登录邮箱账号…

深度学习环境安装与验证指南

目录 1. 项目背景 2. 系统与软件版本 2.1 操作系统与硬件 2.2 软件版本 2.3对应版本 3. 环境配置步骤 3.1 安装 Anaconda 3.2 创建虚拟环境 3.3 安装 CUDA 和 cuDNN 3.3.1 安装 CUDA 11.8 3.3.2 安装 cuDNN 8.6 3.3.3 验证 CUDA 和 cuDNN 安装 3.4 安装 TensorFlo…

国产网卡品牌崛起,做好网络信息安全的“守门人”

在信息技术日新月异的时代背景下&#xff0c;信息安全不仅关乎个人隐私保护&#xff0c;更是国家安全与经济发展的基石。LR-LINK联瑞凭借其前瞻性的视野和深厚的研发实力&#xff0c;成功自主研发出全国产化的FPGA&#xff08;现场可编程门阵列&#xff09;网闸隔离卡方案&…

企业园区智慧升级:电子墨水屏标签打造环保高科技工作空间

“双碳”目标和“数字中国”建设推动企业绿色转型。企业园区作为城市数字化的重要组成部分&#xff0c;传统纸质显示方式已不适应数字化转型的需求。墨水屏技术凭借低功耗和信息便捷更新的优势&#xff0c;成为园区数智显示的新方案&#xff0c;助力绿色数字化转型&#xff0c;…

网络科学导论,网络同步与控制

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

swf怎么转换成mp4格式?视频格式转换,就看这2个办法

许多用户在处理多媒体文件时经常遇到的问题swf文件的转换问题。swf文件作为Flash动画的标准格式&#xff0c;曾经在网页设计和互动媒体中占据重要地位。然而&#xff0c;随着技术的发展和Flash的逐渐淘汰&#xff0c;越来越多的用户需要将swf文件转换为更通用的视频格式&#x…

视频中间件:与海康ISC平台级联

视频中间件是一款对各种视频流的接入处理并能通过标准H5接口输出的安防视频管理软件。其设备兼容性、并发性能、接口输出等方面表现突出&#xff0c;方便了行业跨平台、跨系统的第三方系统和应用对接。在很多的应用场景中&#xff0c;不仅仅是提供标准H5接口与其业务系统的互联…

详细解说数据库课程设计mysql

数据库课程设计中&#xff0c;MySQL是一个非常重要的工具&#xff0c;它是一种关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;广泛应用于各种规模的项目中。下面&#xff0c;我将详细解说在数据库课程设计中如何使用MySQL进行设计。 1. 理解数据库设计的基本…

STM32f103(固件库)

1.工程创建 新建STM32工程 步骤一&#xff1a;创建工程模板文件1.新建一个文件夹 template 2.在文件夹template里面新建六个文件&#xff08;1&#xff09;CMSIS&#xff1a; 存放内核驱动程序和启动引导文件&#xff08;2&#xff09;FWLIB&#xff1a;存放库函数文件&#…

使用twilio向手机发短信做监控报警

最近遇到个需求&#xff0c;就是夜班HW希望有个监控系统指标&#xff0c;如果异常就向监控人手机打电话的需求。在考察以后&#xff0c;发现目前由于国内防电信诈骗的原因&#xff0c;所以想要使用云通讯功能必须由企业去申请&#xff0c;但作为一个个人的监控项目来说太大了。…

【MySQL 15】使用 C/C++ 连接数据库

文章目录 &#x1f308; 一、引入 MySQL 库⭐ 1. 下载库文件⭐ 2. 上传库文件⭐ 3. 引用库文件⭐ 4. 验证是否引入成功 &#x1f308;二、MySQL 接口介绍⭐ 1. 初始化⭐2. 连接数据库⭐ 3. 关闭数据库连接⭐ 4. 设置编码格式⭐ 5. 发送 sql 指令&#x1f319; 5.1 向数据库表中…

Ant Design vue 多层for循环form表单自定义校验

数据结构如下&#xff1a;三维数组。 注意&#xff1a;<a-form-model>一定得写在for外面&#xff01;&#xff01;&#xff01;&#xff01; <!-- 弹出框 --> <a-modal:title"title":dialog-style"{ top: 20px }":visible"visible&quo…

日历显示项目

日历显示&#xff1a; //头文件#ifndef __HEAD_DALY_H__ #define __HEAD_DALY_H__#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <stdint.h>uint32_t months[12]; extern bool is_leap(uint32_t …

02.标准化编程规范

1. 前言 在日常开发中&#xff0c;随着团队人员的变更&#xff0c;功能迭代&#xff0c;如果没有一套规范约束大家&#xff0c;就会导致项目中的代码充斥着各种代码风格&#xff0c;会导致后期维护起来十分困难&#xff0c;所以有必要去制定一套规范。互联网发展到今天&#x…

九月更新|用这个方法,小白在国内也能轻松使用ChatGPT,GPT新手使用手册(编程代码)

一、 ChatGPT可以做什么&#xff1f; ChatGPT能做的事情非常多&#xff01;它不仅仅是一个对话AI。以下是一些主要功能&#xff1a; 1. 回答问题&#xff1a;无论是学术问题、技术问题&#xff0c;还是生活琐事&#xff0c;ChatGPT都能提供帮助。 2. 写作助手&#xff1a;可以…

html+css 实现 带射灯的浮雕按钮

前言:哈喽,大家好,今天给大家分享html+css 实现 带射灯的浮雕按钮!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎点赞、收藏+关注哦 💕 文章目录 效果原理解析1.此按钮效果主要是运用了==css3属性的box-shadow和…

用实时计算释放当下企业大数据潜能

摘要&#xff1a;本文整理自阿里云高级产品解决方案架构师王启华&#xff08;敖北&#xff09;老师在 Flink Forward Asia 2023 中闭门会的分享。内容分为以下五个部分&#xff1a; 1. 实时计算在大数据计算发展中的趋势 2. 实时计算对于企业生产的意义 3. 阿里云飞天大数据产品…

Nginx实验-2

Nginx中的变量 变量可以分为内置变量和自定义变量 内置变量是由nginx模块自带&#xff0c;通过变量可以获取到众多的与客户端访问相关的值 [rootnginx ~]# cd /usr/local/nginx/ [rootnginx nginx]# cd conf.d/ [rootnginx conf.d]# ls status.conf vhost.conf [rootngin…