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

【HarmonyOS】鸿蒙使用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';

@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
@Component
export struct ScanPage {
private TAG: string = '[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 = 480 // 设置预览流高度,默认单位:vp
@State cameraWidth: number = 300 // 设置预览流宽度,默认单位: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()

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

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

}

@Builder 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();
})
}
}

posted @ 2024-12-24 14:57  GeorgeGcs  阅读(93)  评论(0)    收藏  举报