鸿蒙自定义二维码扫描页面

鸿蒙自定义二维码扫描页面

前言

公司的鸿蒙APP功能还不多,最近把想写的一些内容也写的差不多了,还剩一两篇吧,写的内容都是我觉得有点意思,值得一写的,其他常用功能按照官方demo也能做个七七八八。 最近还是打算跳槽了,把最后两篇鸿蒙文章写完,全身心投入吧,不知道行情怎么样。

这篇文章是鸿蒙自定义的二维码扫描页面,官方的扫码功能其实已经不错了,但是产品经理想iOS、Android、鸿蒙一致,没得办法只能做一下了。

效果图

先贴一个效果图吧,觉得可以再看下面代码喽!

WeChat_20240926152030.gif

官方二维码扫描

先来看下官方的二维码扫描:

  qrScan(): Promise<string> {
    
    let options: scanBarcode.ScanOptions = {
      
      scanTypes: [scanCore.ScanType.ALL],
      
      enableMultiMode: false,
      
      enableAlbum: true
    };

    
    return scanBarcode.startScanForResult(this.context, options)
      .then((result: scanBarcode.ScanResult) => {
        LogUtil.d('startScanForResult: ' + result.originalValue);
        return result.originalValue;
      }).catch((error: BusinessError) => {
        let result = `error ${error.code}: ${error.message}`
        LogUtil.e(result);
        return result;
      })
  }

支持多种类型,支持相机和相册的二维码扫描,不要求自定义UI的话,选它就可以了。

官方文档

在自定义扫描界面前,还是看下官方文档吧,虽然很简单,但是可以照着改:

自定义界面扫码

如果要用router来传参或者返回的话,还可以看下这个:

页面路由

自定义扫描页面

下面开始自定义扫码页面,我直接在官方扫码界面上改,加上几点功能,完整代码后面再贴。

添加相册扫码

官方的扫码只支持相机,并不支持相册二维码,这个也简单,加个图片控件,点击通过相册去选图片,再交给detectBarcode识别,识别后丢到相机显示结果的逻辑:

Image($r('app.media.select_from_album'))
.width(20)
.height(20)
.onClick(() => {
  
  let options: scanBarcode.ScanOptions = {
    scanTypes: [scanCore.ScanType.ALL],
    enableMultiMode: true,
    enableAlbum: true
  }
  try {
    
    let photoOption = new picker.PhotoSelectOptions();
    photoOption.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    photoOption.maxSelectNumber = 1;
    let photoPicker = new picker.PhotoViewPicker();
    photoPicker.select(photoOption).then((result) => {
      try {
        
        let inputImage: detectBarcode.InputImage = { uri: result.photoUris[0] }
        
        detectBarcode.decode(inputImage, options).then((result: Array) => {
          
          this.showScanResult(result);
          hilog.info(0x0001, '[Scan Sample]', 'Promise scan result: %{public}s', JSON.stringify(result));
        }).catch((failResult: BusinessError) => {
          hilog.error(0x0001, '[Scan Sample]', 'Promise Error: %{public}s', JSON.stringify(failResult));
        });
      } catch (error) {
        hilog.error(0x0001, '[Scan Sample]', 'Catch Error: failResult: %{public}s', JSON.stringify(error));
      }
    })
  } catch (error) {
    hilog.error(0x0001, '[Scan Sample]', 'error: %{public}s', JSON.stringify(error));
  }
})

增加二维码扫描动画

按理来说这里可以用canvas来实现的,不过不想搞那么复杂,凑合用吧,用了几个控件加个计时器做了个:


RelativeContainer() {
  
  Row().width(20).height(5).backgroundColor("#ffc66f23")
    .alignRules({
      left: { anchor: '__container__', align: HorizontalAlign.Start },
      top: { anchor: '__container__', align: VerticalAlign.Top }
    })
  Column().width(5).height(20).backgroundColor("#ffc66f23")
    .alignRules({
      left: { anchor: '__container__', align: HorizontalAlign.Start },
      top: { anchor: '__container__', align: VerticalAlign.Top }
    })

  
  Row().width(20).height(5).backgroundColor("#ffc66f23")
    .alignRules({
      right: { anchor: '__container__', align: HorizontalAlign.End },
      top: { anchor: '__container__', align: VerticalAlign.Top }
    })
  Column().width(5).height(20).backgroundColor("#ffc66f23")
    .alignRules({
      right: { anchor: '__container__', align: HorizontalAlign.End },
      top: { anchor: '__container__', align: VerticalAlign.Top }
    })

  
  Row().width(20).height(5).backgroundColor("#ffc66f23")
    .alignRules({
      left: { anchor: '__container__', align: HorizontalAlign.Start },
      bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
    })
  Column().width(5).height(20).backgroundColor("#ffc66f23")
    .alignRules({
      left: { anchor: '__container__', align: HorizontalAlign.Start },
      bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
    })

  
  Row().width(20).height(5).backgroundColor("#ffc66f23")
    .alignRules({
      right: { anchor: '__container__', align: HorizontalAlign.End },
      bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
    })
  Column().width(5).height(20).backgroundColor("#ffc66f23")
    .alignRules({
      right: { anchor: '__container__', align: HorizontalAlign.End },
      bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
    })
    .id('scanArea')

  
  Image($r('app.media.scan_line')).width('100%')
    .margin({top: this.scanBarOffset})
    .alignRules({
      left: { anchor: '__container__', align: HorizontalAlign.Start },
      top: { anchor: '__container__', align: VerticalAlign.Top }
    })
}
.width(250)
.height(250)
.margin({top: 150})
.alignRules({
  middle: { anchor: '__container__', align: HorizontalAlign.Center },
  top: { anchor: '__container__', align: VerticalAlign.Top }
})

直接四个Row四个Column围出一个正方形四个角,中间放一个扫描图片,然后加个计时器修改它的marginTop就可以了:

  async onPageShow() {
    
    
    
    this.startScanBar();
  }
  
  private timer: number | null = null;
  private count = 0;
  startScanBar() {
    
    if (this.timer) {
      clearInterval(this.timer);
    }

    
    this.count = 0

    
    this.timer = setInterval(()=>{
      this.count++;
      this.count = this.count % 500;
      
      if (this.count < 250) {
        this.scanBarOffset = this.count;
      }else {
        this.scanBarOffset = 500 - this.count;
      }
    }, 10);
  }

完整代码

和官方案例大差不差,就加了上面两样东西,返回我这直接设置了个静态回调处理,不推荐这么写,可以通过router去拿结果:

import { customScan, detectBarcode, scanBarcode, scanCore } from '@kit.ScanKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { abilityAccessCtrl, common } from '@kit.AbilityKit'
import { router } from '@kit.ArkUI'
import { picker } from '@kit.CoreFileKit'

@Entry
@Component
struct CustomScanPage {
  @State userGrant: boolean = false
  @State surfaceId: string = ''
  
  @State cameraHeight: number = 640
  
  @State cameraWidth: number = 360
  @State scanBarOffset: number = 0
  private mXComponentController: XComponentController = new XComponentController()
  private TAG: string = '[customScanPage]'

  async showScanResult(result: Array) {
    if (result.length > 0) {
      
      hilog.info(0x0001, this.TAG, 'scan result: %{public}s', JSON.stringify(result));
      
      try {
        customScan.stop().then(() => {
          hilog.info(0x0001, this.TAG, 'stop success!');
        }).catch((error: BusinessError) => {
          hilog.error(0x0001, this.TAG, 'stop failed error: %{public}s ', JSON.stringify(error));
        })
      } catch (error) {
        hilog.error(0x0001, this.TAG, 'stop failed error: %{public}s ', JSON.stringify(error));
      }

      

      
      router.back();
    }
  }

  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, 'requestPermissionsFromUser success');
        this.userGrant = true;
      }
    }
  }

  setDisplay() {
    
    this.cameraHeight = 800
    this.cameraWidth = 450
  }

  async onPageShow() {
    await this.requestCameraPermission();
    let options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL],
      enableMultiMode: true,
      enableAlbum: true
    }
    this.setDisplay();
    try {
      customScan.init(options);
    } catch (error) {
      hilog.error(0x0001, this.TAG, 'init fail, error: %{public}s ', JSON.stringify(error));
    }

    
    this.startScanBar();
  }

  async onPageHide() {
    
    this.userGrant = false;
    try {
      await customScan.stop();
    } catch (error) {
      hilog.error(0x0001, this.TAG, 'Catch: stop error %{public}s', JSON.stringify(error));
    }
    try {
      customScan.release().then(() => {
        hilog.info(0x0001, this.TAG, 'release success!');
      }).catch((error: BusinessError) => {
        hilog.error(0x0001, this.TAG, 'release failed error: %{public}s ', JSON.stringify(error));
      })
    } catch (error) {
      hilog.error(0x0001, this.TAG, 'Catch: release error %{public}s', JSON.stringify(error));
    }
  }

  build() {
    Stack() {
      if (this.userGrant) {
        Column() {
          XComponent({
            id: 'componentId',
            type: 'surface',
            controller: this.mXComponentController
          })
            .onLoad(async () => {
              hilog.info(0x0001, this.TAG, 'onLoad is called');
              
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
              hilog.info(0x0001, this.TAG, 'surfaceId: %{public}s ', this.surfaceId);
              let viewControl: customScan.ViewControl = {
                width: this.cameraWidth,
                height: this.cameraHeight,
                surfaceId: this.surfaceId
              };
              
              try {
                customScan.start(viewControl)
                  .then(async (result: ArrayScanResult>) => {
                    
                    this.showScanResult(result);
                  })
              } catch (error) {
                hilog.error(0x0001, this.TAG, 'start fail, error: %{public}s ', JSON.stringify(error));
              }
            })
              
            .width(this.cameraWidth)
            .height(this.cameraHeight)
            .position({ x: 0, y: 0 })
        }
        .height('100%')
        .width('100%')
      }

      RelativeContainer() {

        
        Image($r('app.media.video_back'))
          .width(30)
          .height(30)
          .margin({ top: 50, left: 20 })
          .padding(6)
          .borderRadius(15)
          .backgroundColor("#6666")
          .onClick(() => {
            router.back();
          })
          .alignRules({
            left: { anchor: '__container__', align: HorizontalAlign.Start },
            top: { anchor: '__container__', align: VerticalAlign.Top }
          })

        
        RelativeContainer() {
          
          Row().width(20).height(5).backgroundColor("#ffc66f23")
            .alignRules({
              left: { anchor: '__container__', align: HorizontalAlign.Start },
              top: { anchor: '__container__', align: VerticalAlign.Top }
            })
          Column().width(5).height(20).backgroundColor("#ffc66f23")
            .alignRules({
              left: { anchor: '__container__', align: HorizontalAlign.Start },
              top: { anchor: '__container__', align: VerticalAlign.Top }
            })

          
          Row().width(20).height(5).backgroundColor("#ffc66f23")
            .alignRules({
              right: { anchor: '__container__', align: HorizontalAlign.End },
              top: { anchor: '__container__', align: VerticalAlign.Top }
            })
          Column().width(5).height(20).backgroundColor("#ffc66f23")
            .alignRules({
              right: { anchor: '__container__', align: HorizontalAlign.End },
              top: { anchor: '__container__', align: VerticalAlign.Top }
            })

          
          Row().width(20).height(5).backgroundColor("#ffc66f23")
            .alignRules({
              left: { anchor: '__container__', align: HorizontalAlign.Start },
              bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
            })
          Column().width(5).height(20).backgroundColor("#ffc66f23")
            .alignRules({
              left: { anchor: '__container__', align: HorizontalAlign.Start },
              bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
            })

          
          Row().width(20).height(5).backgroundColor("#ffc66f23")
            .alignRules({
              right: { anchor: '__container__', align: HorizontalAlign.End },
              bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
            })
          Column().width(5).height(20).backgroundColor("#ffc66f23")
            .alignRules({
              right: { anchor: '__container__', align: HorizontalAlign.End },
              bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
            })
            .id('scanArea')

          
          Image($r('app.media.scan_line')).width('100%')
            .margin({top: this.scanBarOffset})
            .alignRules({
              left: { anchor: '__container__', align: HorizontalAlign.Start },
              top: { anchor: '__container__', align: VerticalAlign.Top }
            })

          Row()
            .width(40)
            .height(40)
            .backgroundColor('#20FFFFFF')
            .borderRadius(20)
            .margin({ top: 40 })
            .alignRules({
              middle: { anchor: '__container__', align: HorizontalAlign.Center },
              top: { anchor: 'scanArea', align: VerticalAlign.Bottom }
            })

          
          Image($r('app.media.select_from_album'))
            .width(20)
            .height(20)
            .margin({top:50})
            .alignRules({
              middle: { anchor: '__container__', align: HorizontalAlign.Center },
              top: { anchor: 'tipTwo', align: VerticalAlign.Bottom }
            })
            .onClick(() => {
              
              let options: scanBarcode.ScanOptions = {
                scanTypes: [scanCore.ScanType.ALL],
                enableMultiMode: true,
                enableAlbum: true
              }
              try {
                
                let photoOption = new picker.PhotoSelectOptions();
                photoOption.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
                photoOption.maxSelectNumber = 1;
                let photoPicker = new picker.PhotoViewPicker();
                photoPicker.select(photoOption).then((result) => {
                  try {
                    
                    let inputImage: detectBarcode.InputImage = { uri: result.photoUris[0] }
                    
                    detectBarcode.decode(inputImage, options).then((result: Array) => {
                      
                      this.showScanResult(result);
                      hilog.info(0x0001, '[Scan Sample]', 'Promise scan result: %{public}s', JSON.stringify(result));
                    }).catch((failResult: BusinessError) => {
                      hilog.error(0x0001, '[Scan Sample]', 'Promise Error: %{public}s', JSON.stringify(failResult));
                    });
                  } catch (error) {
                    hilog.error(0x0001, '[Scan Sample]', 'Catch Error: failResult: %{public}s', JSON.stringify(error));
                  }
                })
              } catch (error) {
                hilog.error(0x0001, '[Scan Sample]', 'error: %{public}s', JSON.stringify(error));
              }
            })
        }
        .width(250)
        .height(250)
        .margin({top: 150})
        .alignRules({
          middle: { anchor: '__container__', align: HorizontalAlign.Center },
          top: { anchor: '__container__', align: VerticalAlign.Top }
        })
      }
      .width('100%')
      .height('100%')
    }
    
    .width('100%')
    .height('100%')
  }

  private timer: number | null = null;
  private count = 0;
  startScanBar() {
    
    if (this.timer) {
      clearInterval(this.timer);
    }

    
    this.count = 0

    
    this.timer = setInterval(()=>{
      this.count++;
      this.count = this.count % 500;
      
      if (this.count < 250) {
        this.scanBarOffset = this.count;
      }else {
        this.scanBarOffset = 500 - this.count;
      }
    }, 10);
  }
}

小结

根据官方自定义二维码扫描页面修改,增加了相册图片扫描,和一个方框扫描动画,用起来还可以!

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22347,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?