鸿蒙-验证码输入框的几种实现方式(下)

96 阅读6分钟

在上一篇文章鸿蒙-验证码输入框的几种实现方式(上)中介绍了如何使用多个TextInput来实现验证码输入框, 本篇文章中介绍另外一种思路:自己代码控制、记录键盘输入内容,使用Text组件展示验证码,当然也可以使用Canvas自己绘制

效果图

先放一下效果图

four_text_input.gif four_text.gif canvas_input.gif

多个 Text 拼接

主要难点只有一个,也是最核心的问题:如何记录键盘输入的内容?
这个问题解决了,就只剩下样式、展示输入内容这些简单的东西了。另外样式问题在上一篇中也提到过如何处理。

记录输入内容

展示

输入的内容我们用字符串记录一下,用 Text来展示,为了方便查看,加一些边框.


@State inputStr :string = ""
build() {
    Text(this.inputStr).width('80%').margin({left:'8%',right:'8%'}).borderRadius(20).borderWidth(2).borderColor(Color.Red).height(45)
  }

配置输入法

需要获取到InputMethodController实例,然后设置输入的类型、完成按钮显示的文案等等。

import { inputMethod } from '@kit.IMEKit'
private inputController: inputMethod.InputMethodController = inputMethod.getController();
// 软键盘相关设置
private textConfig: inputMethod.TextConfig = {
inputAttribute: {
    textInputType: inputMethod.TextInputType.NUMBER,
    enterKeyType: inputMethod.EnterKeyType.DONE
    }
};

监听输入、删除事件

文档上给出了各种各样的事件,这里就不再一一列举,选择我们需要的insertTextdeleteLeft时间进行监听。对其他事件感兴趣的可以自己试一下。

  //订阅键盘输入、删除事件
  bindKeyboardEvent(){
    this.inputController.on('insertText', (text) => {
      this.inputStr += text;
    })
    this.inputController.on('deleteLeft', (length) => {
      this.inputStr = this.inputStr.substring(0, this.inputStr.length - length);
    })
  }

  unbindKeyboardEvent(){
    this.inputController.off('insertText')
    this.inputController.off('deleteLeft')
    this.inputController.detach()
  }

这里需要注意的是,这些时间可以被重复添加监听,添加多次则会回调多次,因此,我们在控件展示的时候添加监听,在控件销毁的时候移除监听。
inputController.attach()方法的第一个布尔类型的参数表示是否在attch之后弹起软键盘。如果不需要的话可以设置为false,在后续有需要的时候通过inputController.showTextInput()inputController.hideTextInput()控制软键盘的展示和隐藏。

Text().onAppear(async ()=>{
          await this.inputController.attach(true,this.textConfig).then(()=>{
              this.bindKeyboardEvent()
          }).catch((error:BusinessError)=>{
            hilog.error(0x01,"RecordKeyboardInputPage","输入法绑定出错")
          })
      })
      .onDisAppear(()=>{
        this.unbindKeyboardEvent()
      })

到这里我们就已经能够正确的记录下键盘输入的字符,并且展示在一个Text中了。最大的问题解决了,剩下的就是如何拆到多个Text上展示,这个就简单多了

展示

记录输入已经搞定了,这次用Flex做父布局,Text做子控件来展示一下:


build() {
Flex({
    direction: FlexDirection.Row,
    justifyContent: FlexAlign.SpaceBetween,
    alignItems: ItemAlign.End,
    wrap: FlexWrap.NoWrap,
    space: { main: new LengthMetrics(10) }
}) {
    ForEach(this.verifyCodeIdx, (item: number) => {
    Text(this.inputStr[item])
        .flexGrow(1)
        .flexShrink(1)
        .flexBasis(1)
        .height(50)
        .fontSize(30)
        .fontColor('#323232')
        .enabled(false)
        .textAlign(TextAlign.Center)
        .border({
        style: BorderStyle.Solid,
        width: { bottom: this.inputStr.length==item ? 2 : 1 },
        color: this.inputStr.length==item ? '#018576' : '#bdbdbd'
        });
    });
}
.width('100%') .onAppear(async ()=>{
    await this.inputController.attach(true,this.textConfig).then(()=>{
    this.bindKeyboardEvent()
    }).catch((error:BusinessError)=>{
    hilog.error(0x01,"RecordKeyboardInputPage","输入法绑定出错")
    })
})
.onDisAppear(()=>{
    this.unbindKeyboardEvent()
})
}

这样我们就完成了一个基础的验证码输入框的功能。
哦,还少一个输入完成的回调,这个简单,就在订阅键盘的insertText事件回调里面判断一个字符串长度仿照上一篇做个回调就好了,这里就不再重复说明了。

使用canvas自绘制

这个就是闲着写出来的玩的,一般也不会选择这种方案来实现。 接着上面的内容,同样的方法记录下键盘的输入内容,在insertText事件回调里面通知 canvas 进行绘制

过程拆解

大致上分为两步,画文字,画背景。
这里背景就简单的设置为下划线,使用不同颜色来区分是不是焦点(当前需要输入的)。还是以 4 位验证码为例,画布宽度减去三个间隔后再除以 4,就是每条下划线的长度。
起点坐标为((lineLength+space)*i,canvasHeight-2),
终点坐标为((lineLength+space)*i+lineLength,canvasHeight-2)
文字的中心 x 坐标应当和下划线的中心坐标 x 相同,这样画出的字才不会偏。
我们可以使用CanvasRenderingContext2D.measureText(text:string)来测量文字尺寸,然后计算出来文字的坐标。
这里还得提醒一下,CanvasRenderingContext2D.fillText绘制文字时传入的坐标是文字的左下角坐标,别搞错了。

第一步:画布参数、需要的变量

需要记录画布大小已经绘制需要要的参数

  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private canvasRendering: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private canvasWidth = 0
  private canvasHeight = 0

拆出来画背景和画文字的方法

drawText() {}
drawUnderLine() {}

在输入内容发生变化的时候,我们需要调用drawText()drawUnderLine()方法进行绘制,并且判断输入的文字长度是否为指定长度,打到指定长度后进行回调。这里抽出来方法

  onInputChange() {
    this.canvasRendering.reset()
    this.drawUnderLine()
    this.drawText()
    // 输入给定位数后做一些操作
    if (this.verifyCodeStr.length === this.codeLength) {
      promptAction.showToast({ message: `输入的验证码是-->${this.verifyCodeStr}` })
    }
  }

在键盘的的insertTextdeleteLeft事件监听中调用

this.inputController.on('insertText', (text: string) => {
    this.verifyCodeStr += text;
    this.onInputChange()
});
this.inputController.on('deleteLeft', () => {
    this.verifyCodeStr = this.verifyCodeStr.substring(0, this.verifyCodeStr.length - 1);
    this.onInputChange()
});

画背景

这里就按照上面拆解过程中画线的方法绘制就行了

  drawUnderLine() {
    //每个下划线长度
    let lineWidth = (this.canvasWidth - (this.codeLength - 1) * this.hSpace) / 4
    //画出下划线
    let y = this.canvasHeight - 2
    for (let i = 0; i < this.codeLength; i++) {
      let path = new Path2D()
      if (this.verifyCodeStr.length ==  i) {
        this.canvasRendering.strokeStyle = "#39D167"
        this.canvasRendering.lineWidth = 3
      } else {
        this.canvasRendering.strokeStyle = "#999999"
        this.canvasRendering.lineWidth = 2
      }
      let startX = (lineWidth + this.hSpace) * i
      let endX = lineWidth + (lineWidth + this.hSpace) * i
      path.moveTo(startX, y)
      path.lineTo(endX, y)
      this.canvasRendering.stroke(path)
    }
  }

这里也有需要注意的点,划线时我是用的Path2D路径对象保存的下划线信息而不是直接使用CanvasRenderingContext2D.lineTo(x: number, y: number)这样方法。
这是因为后者有一些意想不到的问题:比如调用this.CanvasRenderingContext2D.clearRect()后再调用 CanvasRenderingContext2D.stroke(),你会发现被 clear 的区域又回来了。

如果在上面这个循环中也使用lineTo方法,虽然我们设置了不同的颜色及宽度,但当我们输入第二个文字时,会发现第一个下划线被绘制了两遍,而且是不同颜色叠加在一起。有兴趣的可以自己试一下,不知道是我的写法有问题还是对文档的理解有问题,还是其他原因就不得而知了。

画文字

这个也不复杂

  drawText() {
    //每个下划线长度
    let lineWidth = (this.canvasWidth - (this.codeLength - 1) * this.hSpace) / 4
    //绘制的文字大小和颜色
    this.canvasRendering.fillStyle = "#666666"
    this.canvasRendering.font = "30vp"
    //文字和下划线的距离
    let y = this.canvasHeight - 6
    for (let i = 0; i < this.codeLength && i < this.verifyCodeStr.length; i++) {
    //在每个下划线的中心画数字:(线长的一半+线的起点 - 文字宽度的一半)
      let result: TextMetrics = this.canvasRendering.measureText(this.verifyCodeStr[i])
      this.canvasRendering.fillText(this.verifyCodeStr[i],
        lineWidth / 2 + (lineWidth + this.hSpace) * i - result.width / 2, y)
    }
  }

这样我们就完成了使用 canvas 绘制的验证码输入框

总结

怎么样,验证码输入框是不是看上去很简单,实际上一点也不难? 只要有了思路,拆解成小步骤,然后又一步步实现就好了。

个人认为常用的就是上一篇的TextInput方案和本篇的Text方法, canvas自绘制一般真用不到,除非是一些奇形怪状的需求


最后还得吐槽一下,鸿蒙的接口参数中有很多很多需要这种硬编码的配置,搞的很烦,就不能用个常量或者枚举值代替一下么???