首页 > 其他分享 >自定义界面扫码,满足应用个性化定制需求

自定义界面扫码,满足应用个性化定制需求

时间:2024-09-09 11:49:12浏览次数:10  
标签:扫码 自定义 相机 TAG customScan error 个性化

二维码识别技术已经成为我们日常生活中不可或缺的一部分,广泛应用于支付、交通、餐饮、生活服务以及智能家居等领域。它不仅是移动应用的重要流量入口,更是连接线上线下世界的桥梁。

不同的App在扫码界面的设计上各展其特色,从页面元素到交互方式,都体现了开发者对用户体验的重视。然而,标准化的扫码界面往往难以满足开发者对个性化定制的追求。例如,开发者可能希望调整扫码页面的标题、优化扫码框的尺寸与位置,甚至定制扫码框的颜色和动画效果。

HarmonyOS SDK 统一扫码服务(Scan Kit)提供了自定义界面扫码能力,开发者可以自行定义扫码的界面样式,让扫码界面更美观,和开发者的应用风格更加匹配。

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

业务流程

image

开发步骤

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

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

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

image

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

4.遵循业务流程完成自定义界面扫码功能。

通过Promise方式回调,调用自定义界面扫码接口拉起相机流并返回扫码结果。

@Entry
@Component
struct CustomScanPage {
  @State userGrant: boolean = false
  @State surfaceId: string = ''
  @State isShowBack: boolean = false
  @State isFlashLightEnable: boolean = false
  @State isSensorLight:boolean = false
  // 设置预览流高度,默认单位:vp
  @State cameraHeight: number = 640
  // 设置预览流宽度,默认单位:vp
  @State cameraWidth: number = 360
  @State cameraOffsetX: number = 0
  @State cameraOffsetY: number = 0
  @State zoomValue: number = 1
  @State setZoomValue: number = 1
  @State scaleValue: number = 1
  @State pinchValue: number = 1
  @State displayHeight: number = 0
  @State displayWidth: number = 0
  private mXComponentController: XComponentController = new XComponentController()
  private TAG: string = '[customScanPage]'

  async showScanResult(result: Array<scanBarcode.ScanResult>) {
    if (result.length > 0) {
      // 获取到扫描结果后暂停相机流
      customScan.stop().then(() => {
        hilog.info(0x0001, this.TAG, 'Succeeded in stopping customScan by promise!');
      }).catch((error: BusinessError) => {
        hilog.error(0x0001, this.TAG,
          `Failed to stop customScan by promise. Code: ${error.code}, message: ${error.message}`);
      })

      // 使用toast显示出扫码结果
      promptAction.showToast({
        message: JSON.stringify(result),
        duration: 5000
      });
      this.isShowBack = true; 
    }
  }

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

  async onPageShow() {
    await this.requestCameraPermission();
    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}`);
    })
  }

  // 自定义扫码界面的顶部返回按钮和扫码提示
  @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: 'surface',
            controller: this.mXComponentController
          })
            .onLoad(async () => {
              hilog.info(0x0001, this.TAG, 'Succeeded in loading, onl oad is called.');
              // 获取XComponent组件的surfaceId
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
              hilog.info(0x0001, this.TAG, `Succeeded in getting surfaceId: ${this.surfaceId}`);
              let viewControl: customScan.ViewControl = {
                width: this.cameraWidth,
                height: this.cameraHeight,
                surfaceId: this.surfaceId
              };
              // 启动相机进行扫码
              // 通过Promise方式回调
              customScan.start(viewControl)
                .then(async (result: Array<scanBarcode.ScanResult>) => {
                  // 处理扫码结果
                  this.showScanResult(result);
                });
              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;
             });
            })// XComponent宽、高,默认单位vp,支持px、lpx、vp
            .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(() => {
                // 点击按钮重启相机流,重新扫码
                customScan.start({ width: 1920, height: 1080, surfaceId: this.surfaceId })
                  .then(async (result: Array<scanBarcode.ScanResult>) => {
                    // 处理扫码结果
                    this.showScanResult(result);
                  })
                this.isShowBack = false;
              })
              .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)
      }
    }
    // 建议相机流设置为全屏
    .width('100%')
    .height('100%')
    .onClick((event: ClickEvent) => {
      if (this.isShowBack) {
        return;
      }
      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) => {
        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),具体可以参考Callback方式回调的示例代码

了解更多详情>>

访问统一扫码服务联盟官网

获取自定义界面扫码服务开发指导文档

标签:扫码,自定义,相机,TAG,customScan,error,个性化
From: https://www.cnblogs.com/HarmonyOSSDK/p/18404269

相关文章

  • java自定义校验注解
    一个简单的自定义规则校验注释,校验图片名是不是.jpg或者.png校验规则的类packagecom.wzw.pdfconverword.validator;importcom.wzw.pdfconverword.annotation.Img;importjakarta.validation.ConstraintValidator;importjakarta.validation.ConstraintValidatorContext;//Im......
  • 17 Python异常处理(捕获异常、抛出异常、自定义异常)
    本篇是Python系列教程第17篇,更多内容敬请访问我的Python合集当我们编写代码时,可能会遇到各种各样的错误情况,比如除数为零、找不到文件、网络问题等等。为了优雅地处理这些问题,Python提供了异常处理机制。1异常处理的基本结构Python中的异常处理主要依赖于try和ex......
  • 鹏哥C语言自定义笔记重点(44-)
    44.不能给地址复制,strcpy拷贝过去连带着\045.46. 47. 48.strstr   //查找子串的一个函数49.strtok  //切割字符串 50.strerror   //返回错误码,所对应的错误信息 检查的是文件51.如果不正确则返回0  52.memcpy负责拷贝两块独立空间......
  • python+flask计算机毕业设计基于web点餐小程序的个性化推荐(程序+开题+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,餐饮业与信息技术的深度融合已成为不可逆转的趋势。特别是在后疫情时代,线上点餐服务因其便捷性、安全性受到了广......
  • 8章8节:绘制自定义的高质量动态图和交互式动态图
    在数据科学和数据可视化的领域,动态图和交互式图形越来越受到重视,因为它们可以帮助用户更好地理解数据并发现潜在的模式。R语言作为数据分析和可视化的强大工具,提供了丰富的功能来创建这些图形。一、认识动态图动态图,顾名思义,是一种可以随时间或某些变量的变化而动态呈现的......
  • springboot个性化大学生线上聊天交友系统
    基于springboot+vue实现的个性化大学生线上聊天交友系统 (源码+L文+ppt)4-017           4系统设计  4.1软件功能模块设计  个性化大学生线上聊天交友分为两个模块,分别是管理员功能模块和用户功能模块。主要功能模块包括:首页、用户、省、市、爱好、......
  • Dotnetty学习笔记——自定义初始化处理器
    常常我们需要开一个服务单,对接不同的客户端,编码器、解码器等都不同,需要针对不同IP添加不同的处理器。publicclassCustomInitializer:Channellnitializer<lSocketChannel>{Action<string,string>_dealMsgAction;lServer_server;publicCustomInitializer(Action<st......
  • 扫码获取微信公众号用户的openid,向某个用户推送消息
    1.生成二维码:生成二维码比较简单的方法是直接使用phpqrcode程序包(可在网上下载得到)。若想获得ThinkPHP支持,需将程序包放在ThinkPHP/Extend/Vendor目录下,让后在程序中引用,代码如下:vendor("phpqrcode.phpqrcode");//要生成二维码的数据$text="扫码后要跳转的页面的url";  ......
  • 【保姆级教程】使用 PyTorch 自定义卷积神经网络(CNN) 实现图像分类、训练验证、预测全
    《博主简介》小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。......
  • SQL 自定义函数 生成网卡地址,MES开发中经常会用到的
    SQL自定义函数生成网卡地址,MES开发中经常会用到的ALTERFunction[dbo].[Fun_ReleaseMACadd]( @CurrentSeqNovarchar(6))Returnsvarchar(18)-------------------------------------------------------------------------------------------------As--------------......