在上一篇文章鸿蒙-验证码输入框的几种实现方式(上)中介绍了如何使用多个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 } };
|
监听输入、删除事件
文档上给出了各种各样的事件,这里就不再一一列举,选择我们需要的insertText
和deleteLeft
时间进行监听。对其他事件感兴趣的可以自己试一下。
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}` }) } }
|
在键盘的的insertText
和deleteLeft
事件监听中调用
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 自绘制一般真用不到,除非是一些奇形怪状的需求
最后还得吐槽一下,鸿蒙的接口参数中有很多很多需要这种硬编码的配置,搞的很烦,就不能用个常量或者枚举值代替一下么???