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

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

效果图

先放一下效果图

多个 Text 拼接

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

记录输入内容

展示

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

1
2
3
4
5

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

配置输入法

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

1
2
3
4
5
6
7
8
9
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时间进行监听。对其他事件感兴趣的可以自己试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//订阅键盘输入、删除事件
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()控制软键盘的展示和隐藏。

1
2
3
4
5
6
7
8
9
10
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做子控件来展示一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

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绘制文字时传入的坐标是文字的左下角坐标,别搞错了。

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

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

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

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

1
2
drawText() {}
drawUnderLine() {}

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

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

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

1
2
3
4
5
6
7
8
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()
});

画背景

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 的区域又回来了。
在上面这个循环中,虽然我们设置了不同的颜色及宽度,但当我们输入第二个文字时,会发现第一个下划线被绘制了两遍,而且是不同颜色叠加在一起。有兴趣的可以自己试一下,不知道是我的写法有问题还是对文档的理解有问题,还是其他原因就不得而知了

画文字

这个也不复杂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 自绘制一般真用不到,除非是一些奇形怪状的需求


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


鸿蒙-验证码输入框的几种实现方式(下)
https://blog.huangyuanlove.com/2024/09/13/鸿蒙-验证码输入框的几种实现方式-下/
作者
HuangYuan_xuan
发布于
2024年9月13日
许可协议
BY HUANG兄