鸿蒙HarmonyOS应用开发-自定义实现验证码框
前言
像是短密码、验证码都有可能需要一个输入框,像是如下:
恰好在写HarmonyOS的时候也需要写一个验证码输入框,但是在实现的时候碰了几次灰,觉得有必要分享下,故有了此篇文章。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。PS:二三为错误示例,如果你只想要代码,在四开始。
ForEach + TextInput
一开始直接上手就是使用Android的老方案,使用多个EditText,只需要切换焦点即可。在HarmonyOS中对应的就是TextInput。因为需要数个相同的输入框,我们先写一个通用的输入框。
@Component struct CodeInputView { build() { TextInput() .backgroundColor("#CCFFFFFF") .borderRadius(10) .maxLength(1) .type(InputType.Number) .align(Alignment.Center) } }
如果一个个去添加输入框,太麻烦了,如果有改动也很头大,所以我们可以塞到一个父布局中,使用ForEach来添加。因为这种情形的输入一般是横向的,使用Row是一个很好的主意,所以变成了“在Row中使用ForEach添加若干个TextInput”,我们稍微修改下:
@Preview @Component struct CodeInputView { // 创建一个包含5个空字符串的数组,用于存储输入的数字 @State codeKids: Array = new Array(5).fill('') // 构建界面 build() { Row({ space: 10 }) { ForEach(this.codeKids, (item: string, index: number) => { TextInput(this.codeKids[index]) .backgroundColor("#CCFFFFFF") // 设置文本输入框的背景颜色 .borderRadius(10) // 设置文本输入框的圆角 .maxLength(1) // 设置最大输入长度为1 .layoutWeight(1) // 设置布局权重 .fontSize(25) // 设置字体大小 .height("100%") // 设置高度为100% .type(InputType.Number) // 设置输入类型为数字 .align(Alignment.Center) // 设置文本居中对齐 }, (item: string) => item) }.backgroundColor(Color.Black) // 设置整个行的背景颜色为黑色,方便preview .height(80) // 设置行的高度为80 } }
如果我们逐个手动添加输入框,会显得非常繁琐,而且如果需要进行修改的话也会变得很复杂。
因此,我们可以将这些输入框放置在一个父布局中,然后使用 ForEach 函数来动态添加它们。由于这种情况下输入框通常是水平排列的,所以使用 Row 组件是一个明智的选择。因此,我们将代码改成了 '在 Row 中使用 ForEach 动态添加多个 TextInput' 的方式。
我们新增了一个名为 codeKids 的数组,并用空字符进行了填充,并使用 @State 注解来修饰它。在 Row 的 ForEach 中,我们直接使用 codeKids 作为数据源,这样输入框的数量会根据 codeKids 数组的长度而变化,而 codeKids 的大小就代表了验证码的长度。
而 layoutWeight(1) 和 { space: 10 } 这两个组合参数,实现了等宽和等间距的效果。
通过@Preview,我们已经能看到效果了。
接下来我们需要它动起来,也就是"输入一个切换到下一个输入框,最后一个返回完整的验证码"。
这里显然需要我们使用onChange方法监听字符的输入。
分解一下
1. 监听每个 TextInput 的 onChange 事件,当用户输入字符后,将字符存入相应位置的 codeKids 数组,并移动焦点到下一个 TextInput。
2. 在最后一个输入框中,当用户输入字符后,将字符存入 codeKids 数组,并触发验证码完成的操作。
需要注意的是,并不能使用focusable(true)来达到将焦点赋予给某个输入框的操作,移动焦点需要使用focusControl.requestFocus(),而requestFocus需要的参数是输入框的key,这里我们需要新增一个key:
@Preview @Component struct CodeInputView { // 用于存储用户输入的字符的数组,初始值为5个空字符串 @State codeKids: Array = new Array(5).fill('') // 回调函数,用于传递输入结果给父组件 inputResultCallback: (string) => void build() { // 创建一个横向排列的行,每个输入框之间有一定的间隔 Row({ space: vp(10) }) { ForEach(this.codeKids, (item: string, index: number) => { TextInput() .backgroundColor("#CCFFFFFF") // 设置文本输入框的背景颜色 .borderRadius(10) // 设置文本输入框的圆角 .maxLength(1) // 设置最大输入长度为1 .layoutWeight(1) // 设置布局权重 .fontSize(25) // 设置字体大小 .height("100%") // 设置高度为100% .type(InputType.Number) // 设置输入类型为数字 .align(Alignment.Center) // 设置文本居中对齐 .key(`code${index}`) // 为每个输入框设置唯一的键 .onChange((value) => { if (value.length void build() { // 使用 Stack 布局组织界面元素 Stack() { if (this.codeKids != null) { // 创建一个横向排列的行,每个字符之间有一定的间隔 Row({ space: vp(10) }) { // 使用 ForEach 循环遍历 codeKids 数组 ForEach(this.codeKids, (item: string, index: number) => { // 显示用户输入的字符 Text(item) .backgroundColor($r('app.color.white_80')) // 设置背景颜色 .height(match()) // 设置高度匹配内容 .layoutWeight(1) // 设置布局权重 .fontSize(fp(25)) // 设置字体大小 .textAlign(TextAlign.Center) // 设置文本水平居中对齐 .align(Alignment.Center) // 设置垂直居中对齐 .borderRadius(vp(15)) // 设置圆角 .focusable(false) // 不可获得焦点 .defaultFocus(false) // 默认不获得焦点 .focusOnTouch(false) // 不在触摸时获得焦点 }, (item: string) => item) } .height(match()) // 设置行的高度匹配内容 .width(match()) // 设置行的宽度匹配内容 // 创建一个输入框用于用户输入 TextInput() .maxLength(this.viewSize) // 设置最大输入长度 .fontSize(fp(25)) // 设置字体大小 .borderRadius(vp(15)) // 设置圆角 .type(InputType.Number) // 设置输入类型为数字 .key(this.inputKey) // 设置唯一的键 .onChange((value) => { // 将输入的字符拆分并分别显示在 Text 组件中 let a = value.split('') this.codeKids.forEach((value, index) => { this.codeKids[index] = a[index] || '' }) if (a.length >= this.viewSize) { // 当达到验证码长度时,触发回调函数传递输入结果 this.inputResultCallback(value) } // 控制光标显示/隐藏 this.showCaret = (a.length == 0) }) .copyOption(CopyOptions.None) // 禁用复制操作 .caretColor(this.showCaret ? Color.Black : Color.Transparent) // 设置光标颜色 .fontColor(Color.Transparent) // 设置文本颜色为透明 .backgroundColor(Color.Transparent) // 设置背景颜色为透明 .height(match()) // 设置高度匹配内容 .width(match()) // 设置宽度匹配内容 } } .height(vp(80)) // 设置整个 Stack 的高度 } }
1. TextInput填充布局,置于顶层。文字和背景设置为透明,隐藏光标。
.copyOption(CopyOptions.None) // 禁用复制操作 .caretColor(Color.Transparent) // 设置光标为透明 .fontColor(Color.Transparent) // 设置文本颜色为透明 .backgroundColor(Color.Transparent) // 设置背景颜色为透明
2. 添加对应数量的Text,用作显示验证码。这一步其实就是将之前的ForEach中添加的TextInput换为Text即可.
3. 在onChange中分隔字符串,并存入对应下标的数组中。
// 将输入的字符拆分并分别显示在 Text 组件中 let a = value.split('') this.codeKids.forEach((value, index) => { this.codeKids[index] = a[index] || '' }) if (a.length >= this.viewSize) { // 当达到验证码长度时,触发回调函数传递输入结果 this.inputResultCallback(value) }
使用也很简单:
CodeInputView({inputResultCallback: (code) => { //做点什么 })
最终效果如下:
最后
只需要稍微的封装下,将输入框的宽度、高度、圆角、颜色、输入类型、数量等包裹在一个对象中,使用@State修饰,并一一对应应用,即可将这个组件做成一个很标准的任意发挥的输入框啦。
唯一的遗憾是,目前没法去除TextInput点击的样式,除非你是纯色(纯色变化看不出来....)
以下就是该例子代码啦:
@Preview @Component export struct CodeInputView { @State viewSize: number = 4 inputResultCallback: (string) => void @Link codeKids: Array @State showCaret: boolean = true private inputKey = "code_input" aboutToAppear() { if (this.codeKids == null) { this.codeKids = new Array(this.viewSize).fill(''); } } build() { Stack() { if (this.codeKids != null) { Row({ space: vp(10) }) { ForEach(this.codeKids, (item: string, index: number) => { Text(item) .backgroundColor($r('app.color.white_80')) .height(match()) .layoutWeight(1) .fontSize(fp(25)) .textAlign(TextAlign.Center) .align(Alignment.Center) .borderRadius(vp(15)) .focusable(false) .defaultFocus(false) .focusOnTouch(false) .onClick(() => { focusControl.requestFocus(this.inputKey) }) }, (item: string) => item) } .height(match()) .width(match()) TextInput() .maxLength(this.viewSize) .fontSize(fp(25)) .borderRadius(vp(15)) .type(InputType.Number) .key(this.inputKey) .onChange((value) => { let a = value.split('') this.codeKids.forEach((value, index) => { this.codeKids[index] = a[index] || '' }) if (a.length >= this.viewSize) { this.inputResultCallback(value) } this.showCaret = (a.length == 0) }) .copyOption(CopyOptions.None) .caretColor(this.showCaret ? Color.Black : Color.Transparent) .fontColor(Color.Transparent) .backgroundColor(Color.Transparent) //TODO 系统问题,如果背景色是透明的也没用,非透明可以 // .stateStyles({ pressed: {.backgroundColor("跟背景一样的颜色(纯透明会黑色闪一下)")}}) .height(match()) .width(match()) } } .height(vp(80)) } }
小结
这个需求大概就告一段了,如果各位有什么想加的功能啥的,可以在评论区告知哦。
总之,HarmonyOS ArkUI的文档还是太少了,很多API都需要摸索,很多写法、操作都不习惯。以及很多坑!,Android的思维不适用在HarmonyOS。希望能跟上这个变化吧。阿弥陀佛。
最后,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(Harmony NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(Harmony NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点。
如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
鸿蒙(Harmony NEXT)最新学习路线
-
HarmonOS基础技能
- HarmonOS就业必备技能
- HarmonOS多媒体技术
- 鸿蒙NaPi组件进阶
- HarmonOS高级技能
- 初识HarmonOS内核
- 实战就业级设备开发
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
《鸿蒙 (OpenHarmony)开发入门教学视频》
《鸿蒙生态应用开发V2.0白皮书》
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》
- 初识HarmonOS内核
- HarmonOS高级技能
- 鸿蒙NaPi组件进阶
- HarmonOS就业必备技能