「HarmonyOS」验证码多TextInput输入框焦点自动跳转问题

news2025/5/25 16:28:43

需求背景:需要做一个多输入框的验证码模块,输入验证码时输入后光标会自动跳转至下一个输入框,删除验证码时会自动删除上一个输入框内容,并且光标跳转至上一个输入框内。6位验证码全部输完后进行登录请求

具体样式如下图:

在这里插入图片描述

具体实现效果如下图:

在这里插入图片描述

思路分析:

1.组件化

这个验证码输入框每个都样式和交互逻辑都非常相似,将其提取出作为组件化编写,减少代码冗余,先将单个输入框的样式进行编写

@Component
struct codeInputView {
  code: string
  keyNo: number
  keyStr: string

  build() {
    Row() {
      Column() {
        TextInput({text: this.code})
          .backgroundColor($r('app.color.smart_F5F5F5'))
          .maxLength(1)
          .type(InputType.Number)
          .align(Alignment.Center)
          .width((screenWidthPxToVp - 87)/6 - 10)
      }
      .width((screenWidthPxToVp - 87)/6)
    }
    .height(55)
    .backgroundColor($r('app.color.smart_F5F5F5'))
    .borderRadius(8)
  }
}
2.实现输入的时候,输入后光标跳转至下一个输入框

通过查阅开发文档和相关资料,发现可以将focusControl和key配合使用进行切换聚焦组件,切换焦点

相关资料链接

// 自定义输入框组件
@Component
struct codeInputView {
  @State code:string
  keyNo: number
  keyStr: string
  // 将code值传递到外部
  transfer: (number) => void
  // 登录请求回调
  requestAction?: ()=>void

  build() {
    Row() {
      Column() {
        TextInput({text: this.code})
          .backgroundColor($r('app.color.smart_F5F5F5'))
          .maxLength(1)
          .type(InputType.Number)
          .align(Alignment.Center)
          .width((screenWidthPxToVp - 87)/6 - 10)
          .onChange((value) => {
            if (value.length == 1) {
              // 当输入框中有验证码输入
              // 当不是最后一个输入框时,焦点跳转到下一个输入框
              if (this.keyNo != 5) {
                // 验证码赋值
                this.code = value
                // 验证码传递至外部
                this.transfer(value)
                // 切换焦点到下一个输入框
                let nextKeyNo: number = this.keyNo + 1
                let nextKeyStr = 'code' + nextKeyNo
                Log.info('nextKeyStr = ' + nextKeyStr)
                focusControl.requestFocus(nextKeyStr)
                
              } else {
              // 当最后一个输入框时,传值并且进行登录请求回调
                this.transfer(value)
                this.requestAction()
              }
            }
          })
          .onFocus(() => {
             // 获取焦点时进行日志打印查看
             Log.info('get focus = ' + this.keyStr)
          })
          // 设置输入框key值
          .key(this.keyStr)
      }
      .width((screenWidthPxToVp - 87)/6)
    }
    .height(55)
    .backgroundColor($r('app.color.smart_F5F5F5'))
    .borderRadius(8)
  }
}

// 页面代码
@Entry
@Component
export default struct VerifyLoginPage {
  private inputVerify: string = '请输入验证码'
  private hadSentToPhone: string = '已发送验证码到您的手机号'
  private code0: string
  private code1: string
  private code2: string
  private code3: string
  private code4: string
  private code5: string

  @State phone: string = ''

  onPageShow() {
    this.phone = router.getParams()?.['phone']
  }

  build() {
    Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Start}) {
      Image($r('app.media.back_icon'))
        .size({width: 33, height: 33})
        .onClick(() => {
          Log.info('click back button')
          router.back()
        })

      Text(this.inputVerify)
        .textStyle(25, $r('app.color.smart_24292B'), FontWeight.Medium)
        .margin({top: 32})

      Text(this.hadSentToPhone + this.phone)
        .textStyle(14, $r('app.color.smart_9DA2A5'), FontWeight.Regular)
        .margin({top: 12})

      // 验证码输入框
      Flex({direction: FlexDirection.Row, alignItems: ItemAlign.Start, justifyContent: FlexAlign.SpaceBetween}) {
        codeInputView({keyNo: 0, keyStr: 'code0', transfer: (num)=>{this.code0 = num}})

        codeInputView({keyNo: 1, keyStr: 'code1', transfer: (num)=>{this.code1 = num}})
          .margin({left: 11})

        codeInputView({keyNo: 2, keyStr: 'code2', transfer: (num)=>{this.code2 = num}})
          .margin({left: 11})

        codeInputView({keyNo: 3, keyStr: 'code3', transfer: (num)=>{this.code3 = num}})
          .margin({left: 11})

        codeInputView({keyNo: 4, keyStr: 'code4', transfer: (num)=>{this.code4 = num}})
          .margin({left: 11})

        codeInputView({keyNo: 5, keyStr: 'code5', transfer: (num)=>{this.code5 = num}, requestAction: ()=>{this.loginRequest()}})
          .margin({left: 11})
      }
      .margin({top: 80})
    }
    .width('100%')
    .padding({left: 16, right: 16})
  }
  
// 登录请求
loginRequest() {
  let code = this.code0 + this.code1 + this.code2 + this.code3 + this.code4 + this.code5
  Log.info('phone = ' + this.phone)
  Log.info('code = ' + code)
  // 登录网络请求
  // xxxxxxx
}

此时,可以实现当输入后,光标可以跳转至下一个输入框中,并且可以将输入的code传递至组件外部进行存储在页面中

3.实现删除时,删除前一个输入框内容,并且光标跳转至前一个输入框中

通过日志打印发现,输入删除按钮,onChange方法无法获取到删除的value值。于是通过查看开发文档,发现可以通过onKeyEvent(event: KeyEvent) => {}方法,获取到删除指令,并且通过打印日志发现,每次点击删除按键,都会执行两次删除方法。

这在一开始非常奇怪和难受,因为我想用同样key的值递减来控制光标的移动,而每次删除时,都会往前跳两个输入框,并且输入框的内容不会删除,所以由此我想到可以一次删除方法来进行光标跳转,一次删除方法用来删除输入框的内容。

以下为更新后最终代码

@Component
struct codeInputView {
  @State code: string = ''
  keyNo: number
  keyStr: string
  transfer: (number) => void
  requestAction?: ()=>void

  build() {
    Row() {
      Column() {
        TextInput({text: this.code})
          .backgroundColor($r('app.color.smart_F5F5F5'))
          .maxLength(1)
          .type(InputType.Number)
          .align(Alignment.Center)
          .width((screenWidthPxToVp - 87)/6 - 10)
          .onChange((value) => {
            if (value.length == 1) {
              if (this.keyNo != 5) {
                let nextKeyNo: number = this.keyNo + 1
                let nextKeyStr = 'code' + nextKeyNo
                Log.info('nextKeyStr = ' + nextKeyStr)
                this.code = value
                this.transfer(value)
                focusControl.requestFocus(nextKeyStr)
              } else {
                this.transfer(value)
                this.requestAction()
              }
            }
          })
          .onFocus(() => {
             Log.info('get focus = ' + this.keyStr)
             Log.info('thisCode = ' + this.code)
          })
          // 删除验证码时执行的方法回调
          .onKeyEvent((event: KeyEvent) => {
            if (event.keyCode == KeyCode.KEYCODE_DEL) {
              // 当code值不为空时,删除code值
              if (this.code != '') {
                this.code = ''

              } else if (this.keyNo > 0) {
              // 如果不是第一个输入框,则每次递减keyNo使光标进行跳转至前一个输入框内
                let preKeyNo: number = this.keyNo - 1
                let preKeyStr = 'code' + preKeyNo
                Log.info('preKeyStr = ' + preKeyStr)
                focusControl.requestFocus(preKeyStr)
              }
            }
          })
          .key(this.keyStr)
      }
      .width((screenWidthPxToVp - 87)/6)
    }
    .height(55)
    .backgroundColor($r('app.color.smart_F5F5F5'))
    .borderRadius(8)
  }
}

// 页面代码
// 页面代码与第2点相同,在此不过多赘述

此次在codeInputView组件的初始化中code值有进行修改,添加了’'空字符串,这是因为通过打印日志发现,如果不进行赋值的初始化时,code值会为undefined,这个在删除code值时会非常奇怪,所以进行了设空字符串进行初始化

最终即可实现需求所要求的交互逻辑

当前HarmonyOs仍在初步学习过程中,大家如果感兴趣或者有问题可以一起沟通交流,目前在学习过程中也遇到一些HarmonyOs开发组件上的一些小bug,希望华为可以及时进行更新修复!努力并大步的发展我们国家自己的移动端系统,遥遥领先!

如果该文章对你有所帮助的话,可以点赞、收藏并关注一下!后续会持续更新更多技术内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1355803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RFID数据中心智能资产管理系统

数据中心机房承担着保障企业关键数据处理的重要责任,机房的日常管理直接关系到整体机房的日常维护和运行安全,数据资产管理中心在监管机房各部分设备的运行情况、维护数据中心的资产方面发挥着重要的作用。 成功的数据中心机房管理不仅需要选择高可靠性…

理解UML中的依赖关系

理解UML中的依赖关系 在面向对象的设计中,理解各种类之间的关系对于构建一个清晰、可维护的系统至关重要。UML(统一建模语言)为我们提供了一种可视化这些关系的方式。今天,我们将深入探讨UML中的依赖关系(Dependency&a…

Kubernetes 核心实战之一(精华篇 1/2)

文章目录 1,资源创建方式1.1 yaml1.2 命令行 2,NameSpace命名空间2.1 命令行创建ns2.2 yaml 创建ns 3,Pod3.1 命令行 创建pod3.2 yaml 创建pod3.3 可视化界面 创建3.3.1 Pod nginx3.3.2 Pod nginx tomcat3.3.3 Pod 2ngnix 1,资源…

ThreadLocal线程重用导致用户信息错乱的 Bug

在生产上遇到一个诡异的问题,有时获取到的用户信息是别人的。查看代码后,我发现他使用了 ThreadLocal 来缓存获取到的用户信息。 我们知道,ThreadLocal 适用于变量在线程间隔离,而在方法或类间共享的场景。如果用户信息的获取比较…

软件测试第二部分:白盒测试

概念与定义 白盒测试:侧重于系统或部件内部机制的测试,类型分为分支测试(判定节点测试)、路径测试、语句测试。 控制流分析(基于程序结构):控制流分析是一类用于分析程序控制流结构的静态分析技术,目的在于…

8个流行的Python可视化工具包,你喜欢哪个?

用 Python 创建图形的方法有很多,但是哪种方法是最好的呢?当我们做可视化之前,要先明确一些关于图像目标的问题:你是想初步了解数据的分布情况?想展示时给人们留下深刻印象?也许你想给某人展示一个内在的形…

芯课堂 | LVGL基础知识(二)

引言 在 LVGL 中,用户界面的基本构建块是对象,也称为小部件(widget)。默认情况下,LVGL在背景上绘制旧对象,在前景上绘制新对象。 对象层级(Layers) 创建对象层级顺序 默认情况下,LVGL在背景上绘制旧对象&#xff0c…

【导出与导入Virtualbox虚拟机和启动连接openGauss数据库】

【导出与导入Virtualbox虚拟机和启动连接openGauss数据库】 一、导出虚拟机二、导入虚拟机三、启动数据库四、使用Data Studio连接数据库 一、导出虚拟机 选择关机状态的虚拟机 -> 管理菜单 -> 导出虚拟电脑 点击完成后,需要等待一小段时间,如…

基于gitlab 12.8.0版本的完整镜像过程

目前已在一台服务器上安装了gitlab 12.8.0,并且稳定运行了有几年了,其上面也创建了大量的项目。目前要求对该gitlab及其上面的所有仓库做一个完整的镜像。具体操作过程如下: 1、确认现有的gitlab的版本号 2、到gitlab官网下载相同版本号的gi…

【干货】Windows中定时删除system32目录下的.dmp文件教程

旭帆科技的技术人员除了给用户答疑解惑以外,还会主动测试软件性能,进行平台优化,除此之外,技术人员还会总结一些技术干货,这不,近期又提供了一份如何在Windows中定时删除system32目录下的.dmp文件的教程。感…

目标检测COCO数据集与评价体系mAP

1.mAP 2.IoU IoU也就是交并比,也称为 Jaccard 指数,用于计算真实边界框与预测边界框之间的重叠程度。它是真值框与预测边界框的交集和并集之间的比值。Ground Truth边界框是测试集中手工标记的边界框,用于指定对象图像的位置以及预测的边界框…

软件安全测试有哪些测试方法?安全测试报告如何申请?

随着网络条件越来越好,软件产品质量和软件安全处于同等重要的地位,一款好的软件产品安全系数至关重要,因此安全测试必不可少,那么软件安全测试有哪些测试方法呢?安全测试报告又该如何申请? 软件安全测试是指测试人员…

BUUCTF--ciscn_2019_n_31

这是一题32位的堆题,照常看看保护: 没有开启PIE,接着进行黑盒测试: 菜单题,扔进IDA看看代码逻辑: 4这个功能是提供所谓的进阶版,当时我测试的时候以为是里面有后门还是什么的。结果发现是虚晃一…

(17)Linux的进程阻塞进程程序替换 exec 函数簇

前言:本章我们讲解它的 options 参数。在讲解之前我们需要理解进程阻塞,然后我们重点讲解二进程程序替换,这是本章的重点,然后介绍一个进程替换函数 execl,通过介绍这个函数来打开突破口,引入进程创建的知识…

dubbo使用的三种配置

一. 准备注册中心 dubbo的注册中心在生产环境中,一般都会选择 ZooKeeper 下载 ZooKeeper ZooKeeper_3.4.14下载地址启动ZK # 解压安装包 tar -zxvf zookeeper-3.4.14.tar.gz# 进入安装目录, cp conf/zoo_sample.cfg conf/zoo.cfg# 启动ZK ./bin/zkServ…

Vue3-33-路由-路由的别名配置 alias

别名的作用 路由中的别名配置,可以实现 多个路径 对应 同一个路由。 例如 : 路由的路径是 /a; 配置别名为 : /a2; 则 访问 /a 或 /a2 的时候,都可以访问到 同一个组件。 别名的特点 关键字 : alias 当通过别名进行路由…

【期末复习向】数据可视化技术

一、重点复习 题型:填空题(15道,2分一个)与简答题(3道题目,10分一个)与绘图题(选画2个类型的图) 1.什么是数据可视化 在计算机视觉领域,数据可视化是对数据的…

Docker入门教程(详解)

Docker容器化 一 入门 1. 引言 (1)单机部署 场景: 将多个应用部署一台服务器上。 问题 每个应用软件,都会消耗物理资源,共用计算机资源,彼此之间会形成竞争关系。 (2)多机部署 …

技术大拿私房课:掌握Task、Thread、ThreadPool的终极秘籍!

大家好,我是小米!在这个充满技术和创新的时代,作为一名喜欢分享的技术探索者,我想和大家聊一聊一些在社招面试中常常被提到的热门话题——task、thread、threadpool。这是一组关于并发编程的核心问题,也是我们在日常工…

速学python·变量和类型

变量是什么 变量是在计算复杂程序过程中,用于保存中间结果的东西,这个东西一般是可变的量,也就是变量。 例如: 计算方差 1.计算平均值 2.计算每个数字与平均值的差值再平方 3.相加每一项平方值 4.除项数 Average (77.588.599.510…