2024年HarmonyOS鸿蒙最全HarmonyOS一杯冰美式的时间 -- 验证码框,直播app测试面试题

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

一、前言

像是短密码、验证码都有可能需要一个输入框,像是如下:

恰好在写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<string> = 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 注解来修饰它。在 RowForEach 中,我们直接使用 codeKids 作为数据源,这样输入框的数量会根据 codeKids 数组的长度而变化,而 codeKids 的大小就代表了验证码的长度。

layoutWeight(1){ space: 10 } 这两个组合参数,实现了等宽和等间距的效果。

通过@Preview,我们已经能看到效果了。

接下来我们需要它动起来,也就是"输入一个切换到下一个输入框,最后一个返回完整的验证码"。

这里显然需要我们使用onChange方法监听字符的输入。

分解一下

  1. 监听每个 TextInputonChange 事件,当用户输入字符后,将字符存入相应位置的 codeKids 数组,并移动焦点到下一个 TextInput
  2. 在最后一个输入框中,当用户输入字符后,将字符存入 codeKids 数组,并触发验证码完成的操作。

需要注意的是,并不能使用focusable(true)来达到将焦点赋予给某个输入框的操作,移动焦点需要使用focusControl.requestFocus(),而requestFocus需要的参数是输入框的key,这里我们需要新增一个key:

 @Preview
 @Component
 struct CodeInputView {
   // 用于存储用户输入的字符的数组,初始值为5个空字符串
   @State codeKids: Array<string> = 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 <= 1) {
               this.codeKids[index] = value // 存储用户输入的字符
             }
             if (index - 1 < this.codeKids.length) {
               let nextIndex = index + 1
               // 将焦点自动移动到下一个输入框
               focusControl.requestFocus(`code${nextIndex}`)
             } else {
               // 触发验证码完成回调函数
               this.inputResultCallback(this.codeKids.join(""))
             }
           })
       }, (item: string) => item)
     }
     .backgroundColor(Color.Black) // 设置整个行的背景颜色为黑色
     .height(80) // 设置行的高度为80
   }
 }

在新的代码中

  1. inputResultCallback属性:新增了一个名为 inputResultCallback 的属性,用于在用户完成输入后将结果传递给父组件。

  2. TextInput的onChange事件:在每个 TextInput 组件中添加了 onChange 事件处理程序。当用户输入内容时,这个事件处理程序会被触发。在事件处理程序内部,会进行以下操作:

    • 检查输入的值长度是否小于等于1,如果是则将该值存储在 codeKids 数组的相应位置上,以保证每个输入框只能输入一个字符。
    • 检查是否还有下一个输入框(index + 1 是否小于 codeKids 数组的长度)。如果有下一个输入框,将焦点自动移动到下一个输入框,以方便用户连续输入。
    • 如果没有下一个输入框,触发 inputResultCallback 回调函数,将输入的值传递给父组件或其他调用者。
  3. key属性:为每个 TextInput 组件添加了 key 属性,以确保focusControl.requestFocus的正确触发,这里我们使用了 index 来生成唯一的键。

三、奇怪的问题

  1. 输入框没有焦点

第一次初始化的时候并没有获取焦点,系统也不知道焦点给谁。

我们只需要在TextInput中加入

 .defaultFocus(index == 0)

  1. 删除onChange方法并不会触发

整个流程都已经完成了,包括删除验证码!

 if (value.length <= 1) {
    this.codeKids[index] = value 
 }

这段代码赋予了当被删除的时候,数组中的值也会正确的改变。但是!

令人奇怪的是,在当前版本中当进行删除操作的时候,onChange方法并不会触发(平板、模拟器、手机均不会),所以我们需要另寻它法。

监听onKeyEvent!

 .onKeyEvent((event)=>{
   if (event.keyCode == KeyCode.KEYCODE_DEL) {
     
   }
 })

事实上,想法是美好的,这个方法也不会触发(模拟器、平板不触发、手机触发异常)

  1. 软键盘显示异常
 focusControl.requestFocus(nextKeyStr)

使用requestFocus的确可以将焦点切换到下一个输入框,但是软键盘确收起来了!

在这里我试了很多种办法。都没法做到尽善尽美。

多方查证,也觉得TextInput来做这个应该是不可行的,只能等官方下场修复。

那怎么办呢?

四、反过来想 Text() + TextInput()

如果多个输入框有问题,那么我用一个输入框不就行了?于是我就想到了使用多个Text(),一个TextInput的方案。

多个Text()用于排列显示,TextInput用于处理输入

只要显示正常,感知正常,那就没人知道怎么输入进去的~

 @Preview
 @Component
 struct CodeInputView {
   // 用于存储用户输入的字符的数组,初始值为5个空字符串
   @State codeKids: Array<string> = new Array(5).fill('')
 ​
   // 回调函数,用于传递输入结果给父组件
   inputResultCallback: (string) => 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) // 设置背景颜色为透明



![img](https://img-blog.csdnimg.cn/img_convert/f3b178d57c27854162c56b6365130f7d.png)
![img](https://img-blog.csdnimg.cn/img_convert/54bb6bc7e2248135ad45374012c77593.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

nsparent) // 设置背景颜色为透明



[外链图片转存中...(img-qQnW16jx-1715614745510)]
[外链图片转存中...(img-4PCi6Zjz-1715614745510)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 28
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值