【HarmonyOS NEXT】鸿蒙使用ScanKit实现自定义扫码 (二)之解析相册图片二维码

【HarmonyOS NEXT】鸿蒙使用ScanKit实现自定义扫码 (二)之解析相册图片二维码

一、业务流程:

在这里插入图片描述

二、实现思路:

1.从相册中选图,目前最简单的方式是 PhotoPicker

import { photoAccessHelper } from '@kit.MediaLibraryKit';

  /**
   * 去相册选择图片
   */
  onClickSelectPhoto = ()=>{
    try {
      let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      // 设置筛选过滤条件
      PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      // 选择用户选择数量
      PhotoSelectOptions.maxSelectNumber = 1;
      // 实例化图片选择器
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      // 唤起安全相册组件
      photoPicker.select(PhotoSelectOptions, (err: BusinessError, PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
        if (err) {
          console.error(this.TAG, "onClickSelectPhoto photoPicker.select error:" + JSON.stringify(err));
          return;
        }
        // 用户选择确认后,会回调到这里。
        console.info(this.TAG, "onClickSelectPhoto photoPicker.select successfully:" + JSON.stringify(PhotoSelectResult));

      });
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error(this.TAG, "onClickSelectPhoto photoPicker.select catch failed:" + JSON.stringify(err));
    }
  }

2.将拿到的图片信息给scanKit提供的decode接口进行解析二维码

import { customScan, scanBarcode, scanCore, detectBarcode } from '@kit.ScanKit'

 /**
   * 解析图片码数据
   */
  private detectPhoto(uri: string){
    if(uri){
        let inputImg: detectBarcode.InputImage = {
          uri: uri,
        };
        let setting: scanBarcode.ScanOptions = {
          scanTypes: [
            scanCore.ScanType.ALL
          ],
          // 开启识别多码
          enableMultiMode: true,
          enableAlbum: true,
        };
      try {
        // 调用图片识码接口
        detectBarcode.decode(inputImg, setting).then((result: Array<scanBarcode.ScanResult>) => {
          console.info(this.TAG, " decode res: " + JSON.stringify(result))

        }).catch((error: BusinessError) => {
          console.error(this.TAG, " decode error: " + JSON.stringify(error))
        });
      } catch (error) {
        console.error(this.TAG, " catch error: " + JSON.stringify(error))
      }
    }else{
      promptAction.showToast({
        message: "图片数据异常!" + uri
      })
    }
  }

3.处理解析结果,图片占位(无码提示,多码绘图用户选择,单码结果处理)【不展开】

三、源码示例:

import { customScan, scanBarcode, scanCore, detectBarcode } from '@kit.ScanKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { abilityAccessCtrl, common } from '@kit.AbilityKit'
import { display, promptAction } from '@kit.ArkUI'
import { photoAccessHelper } from '@kit.MediaLibraryKit';


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;
}



export struct ScanPage {
  private TAG: string = '[customScanPage]';

   userGrant: boolean = false // 是否已申请相机权限
   surfaceId: string = '' // xComponent组件生成id
   isShowBack: boolean = false // 是否已经返回扫码结果
   isFlashLightEnable: boolean = false // 是否开启了闪光灯
   isSensorLight: boolean = false // 记录当前环境亮暗状态
   cameraHeight: number = 480 // 设置预览流高度,默认单位:vp
   cameraWidth: number = 300 // 设置预览流宽度,默认单位:vp
   cameraOffsetX: number = 0 // 设置预览流x轴方向偏移量,默认单位:vp
   cameraOffsetY: number = 0 // 设置预览流y轴方向偏移量,默认单位:vp
   zoomValue: number = 1 // 预览流缩放比例
   setZoomValue: number = 1 // 已设置的预览流缩放比例
   scaleValue: number = 1 // 屏幕缩放比
   pinchValue: number = 1 // 双指缩放比例
   displayHeight: number = 0 // 屏幕高度,单位vp
   displayWidth: number = 0 // 屏幕宽度,单位vp
   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}`);
    })
  }

  /**
   * 去相册选择图片
   */
  onClickSelectPhoto = ()=>{
    try {
      let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      // 设置筛选过滤条件
      PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      // 选择用户选择数量
      PhotoSelectOptions.maxSelectNumber = 1;
      // 实例化图片选择器
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      // 唤起安全相册组件
      photoPicker.select(PhotoSelectOptions, (err: BusinessError, PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
        if (err) {
          console.error(this.TAG, "onClickSelectPhoto photoPicker.select error:" + JSON.stringify(err));
          return;
        }
        // 用户选择确认后,会回调到这里。
        console.info(this.TAG, "onClickSelectPhoto photoPicker.select successfully:" + JSON.stringify(PhotoSelectResult));
        // 因为设置的选择个数为1,所以直接取0位
        this.detectPhoto(PhotoSelectResult.photoUris[0]);
      });
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error(this.TAG, "onClickSelectPhoto photoPicker.select catch failed:" + JSON.stringify(err));
    }
  }

  /**
   * 解析图片码数据
   */
  private detectPhoto(uri: string){
    if(uri){
        let inputImg: detectBarcode.InputImage = {
          uri: uri,
        };
        let setting: scanBarcode.ScanOptions = {
          scanTypes: [
            scanCore.ScanType.ALL
          ],
          // 开启识别多码
          enableMultiMode: true,
          enableAlbum: true,
        };
      try {
        // 调用图片识码接口
        detectBarcode.decode(inputImg, setting).then((result: Array<scanBarcode.ScanResult>) => {
          console.info(this.TAG, " decode res: " + JSON.stringify(result))
          // 长度大于0说明有解析结果
          if(result.length > 0){
            // 长达大于1 多码
            // 解析码值结果跳转应用服务页
            this.scanResult = result;
            this.isShowBack = true;

            if(result.length > 1){

            }else{
              this.showScanResult(result[0]);
            }
          }else{
            promptAction.showToast({
              message: "无二维码数据!"
            });
          }
        }).catch((error: BusinessError) => {
          console.error(this.TAG, " decode error: " + JSON.stringify(error))
        });
      } catch (error) {
        console.error(this.TAG, " catch error: " + JSON.stringify(error))
      }
    }else{
      promptAction.showToast({
        message: "图片数据异常!" + uri
      });
    }
  }

  /**
   * 用户申请权限
   * @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;
    });
  }

  // 自定义扫码界面的顶部返回按钮和扫码提示
  
  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%')
  }

   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 })
  }

   ScanView(){
    Stack() {

      Column() {
        if (this.userGrant) {
          this.ScanKitView()
        }
      }
      .height('100%')
      .width('100%')
      .backgroundColor(Color.Red)

      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('ReScan')
              .onClick(() => {
                try {
                  customScan.rescan();
                } catch (error) {
                  console.error(this.TAG, 'customScan.rescan err: ' + JSON.stringify(error));
                }

                // 点击按钮重启相机流,重新扫码
                this.startCamera();
              })
              .visibility(this.isShowBack ? Visibility.Visible : Visibility.None)

            // 选择相册图片解析二维码
            Button('相册')
              .onClick(() => {
                  this.onClickSelectPhoto();
              })
          }

          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($r("app.media.icon_select_dian"))
            .width(20)
            .height(20)
            .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}`);
        }
      }))
  }

  private mNavContext: NavDestinationContext | null = null;

  build() {
    NavDestination(){
      this.ScanView()
    }
    .width("100%")
    .height("100%")
    .hideTitleBar(true)
    .onReady((navContext: NavDestinationContext)=>{
      this.mNavContext = navContext;
    })
    .onShown(()=>{
      this.onPageShow();
    })
    .onHidden(()=>{
      this.onPageHide();
    })
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GeorgeGcs

感谢认可!有任何技术疑问欢迎讨

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值