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

文章目录

    • 效果图、优缺点
        • 多TextInput
        • 多 Text
        • Canvas 绘制
    • 多个 TextInput 拼接
      • 放置四个输入框
      • 焦点移动
        • 输入时向后移动
        • 输入完成回调
        • 删除时向前移动
      • 防止点击
      • 总结

最近在做应用鸿蒙化,说白了就是把原来AndroidiOS的代码重新用ArkTS写一遍,我负责基础建设和登录模块,有个验证码输入框需要定制一下外观样式。这里详细记录一下探索过程及结果,以及思路和源码。这里给出了三种方案
一:多个 InputText 拼接,每个 InputText 只能输入 1 个字符,代码控制焦点移动,
二:多个 Text 拼接,通过系统apiinputMethod.InputMethodController控制键盘弹起并记录输入内容,刷新到 Text 中展示
三:使用 Canvas 自己绘制。
由于篇幅较长,这个拆成两篇来介绍。本篇介绍前两种方式,也是最常见的方式。使用Canvas 自己绘制纯粹就是闲着写的东西,后面再介绍。

效果图、优缺点

先放一下效果图

多TextInput

优点:只需要控制焦点就好,键盘的弹起、收起以及输入的内容我们不需要自己去监听,并且除了边框颜色之外,输入框内会有光标闪烁,也能给用户更强一些的提示
缺点:需要控制没有获取到焦点的输入框不能点击、不能长按等。尝试多种方案(设置 enable、focusable 等)均失败后,决定在输入框上面覆盖一个空白透明且大小和输入框相等的 Text 解决这个问题。有其他方案可以告诉我一下

多 Text

优点:不用控制焦点,只需要使用变量控制一下样式就好
缺点:需要自己记录键盘输入的文字,并且没有光标闪烁,当前也可以自己搞个 gif 图或者写个动画来模拟光标

Canvas 绘制

优点:我真的没想到有啥优点,可以自由的绘制边框、底色也算么?但现在的 pai 中有各种各样的Modifier来修改各种属性,实现自己绘制
缺点:全都得自己画,挺麻烦的

多个 TextInput 拼接

这里用四位验证码做例子:
思路挺简单的,四个TextInput并排放一块,输入框限制输入 1 个字符,用Flex做父控件也行,用Row做父控件也行,无所谓,这不是重点。
组件刚出现时,使用getUIContext().getFocusController().requestFocus(key:string)将焦点放在第一个输入框上,键盘就可以弹出来
监听TextInputonDidDeleteonChange或者onDidInsert事件,来判断下一个焦点放在哪个位置
当最后一个TextInput有输入字符时,认为输入完成,进行回调。

下面详细介绍一下每一步怎么做的,以及对应的想法

放置四个输入框

当前获取到焦点的输入框颜色要明亮一些,没有焦点的输入框颜色要暗淡一些。我们使用@Extend()做一个公用样式,传入当前是否是焦点控件来控制边框颜色

@Extend(TextInput)
function textInputStyle(enable:boolean){.border({width: 1,color: enable?"#1b91e0":"#999999",radius: 4,style: BorderStyle.Solid,}).textAlign(TextAlign.Center).layoutWeight(1).maxLength(1).maxLines(1).type(InputType.Number).layoutWeight(1).height(40)
}

为了在焦点变化的时候输入框背景能同步修改,这里用一个@State修饰的布尔数组表示哪个输入框获取焦点。同时为了省事,也定义了另外一个数组,方便使用 ForEach循环渲染。定义另外一个字符串数组来记录每个输入框的内容

@State inputValue: string[] = ["", "", "", ""]// 输入框的内容
@State inputEnable: boolean[] = [true, false, false, false] //输入框是否获取焦点
inputIndex: number[] = [0, 1, 2, 3] //ForEach渲染用build() {Row() {//这里用 Flex 更方便一些,直接设置间距就好,不用这样设置 margin 了ForEach(this.inputIndex, (index: number) => {TextInput({ text: this.inputValue[index] }).id(index.toString())//这里 id 是给FocusController使用.margin({ right: index == this.inputIndex.length - 1 ? 0 : 10 }).textInputStyle(this.inputEnable[index])})}}

这样我们就画出来了最基本的布局。但我们会发现进入页面后键盘不能自己弹出来,因为输入框没有获取到焦点。
这里有两个方案,一个是给其中TextInput设置defaultFocus为 true,或者在页面展示的时候设置焦点

TextInput({ text: this.inputValue[index] }).defaultFocus(this.inputEnable[index])
//或者
Row(){}.onAppear(()=>{this.getUIContext().getFocusController().requestFocus("0")})

注意这里requestFocus()方法传入的参数"0",也就是上面TextInputid的值.
这样我们就做好的基本的属性,并且页面显示的时候也可以弹出键盘了。

在这里插入图片描述

焦点移动

输入时向后移动

但是这时候我们点击键盘输入的时候,发现光标并不会自动移动到下一个输入框上继续输入。这里需要我们进行控制。这个也比较简单,TextInput有一个输入内容发生变化时,触发的回调:onChange(callback: EditableTextOnChangeCallback)。我们可以在这个方法里面移动焦点

TextInput().onChange((value: string, previewText?: PreviewText) => {
this.inputValue[index] = value //记录输入的内容
if (value.length == 1) { //确认是输入而不是删除if (index != 3) {//如果不是最后一个输入框发生的输入事件,就把焦点交给下一个输入框this.inputEnable[index+1] = true//记录下一个输入框获取焦点,改变背景色this.getUIContext().getFocusController().requestFocus((index + 1).toString())//下一个输入框获取焦点this.inputEnable[index] = false//标记当前输入框失去焦点,改变背景色} else {//如果是最后一个输入框发生的输入事件,表示已经输入完了,继续后面流程//todo 输入完成,继续后面流程}
}
})
输入完成回调

这就很简单了,定义一个函数变量,接收父布局传进来的函数,输入完成时回调这个函数就好

onFinishInput?: (value: string) => void  //函数变量
//输入完成,进行回调
if (this.onFinishInput) {//判断一下空值,然后把保存的值拼接成字符串回调let result = ""for (let i = 0; i < this.inputValue.length; i++) {result += this.inputValue[i]}this.onFinishInput(result)
}

到这里,我们已经实现了大部分功能:输入字符、保存字符、焦点向后移动、输入完成时回调。
下面要解决的就是删除

删除时向前移动

这里需要注意一下:
如果当前输入框有内容,点击删除时删除当前输入框内容,焦点不动:仅在最后一个输入框会有这个情况
如果当前输入框没有内容,则删除上一个输入框内容,焦点移动到上一个输入框
如果当前时第一个输入框,不做处理

刚开始想着在onChange事件中做处理,但当输入框中没有内容时,点击删除的时是没有回调的。怎么搞,翻翻文档,找到了onWillDeleteonDidDelete,并且这两个回调都是发生在onChange之前。
这里我选择了onDidDelete事件中处理,逻辑就是上面说的那样

TextInput().onDidDelete((_) => {if (this.inputValue[index].length == 0) {//不是第一个输入框 且 输入框内没有文字,则删除上一个输入框内容,并且使上一个输入框获取焦点if (index != 0) {this.inputValue[index-1] = ""this.inputEnable[index] = falsethis.inputEnable[index-1] = truethis.getUIContext().getFocusController().requestFocus((index - 1).toString())} else {//如果输入框内有文字,则只删除当前输入框内容this.inputValue[index] = ""}}
})

到这里我们就完成了大部分的工作,一个可以正常工作,随便调整背景的验证码输入框就完成了。
但似乎还有一点问题,当点击非当前焦点的输入框时,光标会移动到点击的输入框中。这就不太好了

防止点击

一开始想法很简单,我们不是有个布尔类型数组保存着当前哪个输入框获取焦点么?没有焦点的输入框设置enable为false就可以了哇,移动焦点的时候先将目标输入框设置enable为true,然后再移动焦点就好了哇。
那就给输入框加上这个设置就好了哇

TextInput().enabled(this.inputEnable[index])

我们在上面的代码中,都是先修改inputEnable数组值,然后再设置焦点,完美
运行一下,页面出现时弹出键盘,除了第一个输入框其他输入框点击都没有反映,很好
试着输入一下,崩了。。。。
日志提示组件不存在或者时不可用状态

Error message:The component doesn't exist, is currently invisible, or has been disabled.

难道是因为在请求焦点的时候,输入框的属性还没来得及完成修改?
做个测试,延迟1s设置焦点,果然是可以的。但这种效果太难受了,键盘会先收起来再弹出来。缩短延迟时间也很难把握时长。那就再翻翻api,找找下一帧之类的回调。
还真有,在UIContext这个类中有一个函数postFrameCallback:注册一个在下一帧进行渲染时执行的回调。按照示例把代码撸好,编译运行,尝试输入,又又又崩了,错误信息也一样。
没办法了么?只能用延迟么?太难受了哇,来个曲线救国,我们搞个透明没有内容的控件覆盖在没有焦点的输入框上不就行了么。
于是我们得到了这样的代码

Row(){ForEach(this.inputIndex,(index:number)=>{RelativeContainer(){TextInput({text:this.inputValue[index]})if(!this.inputEnable[index]){//没有焦点则覆盖一个空白TextText().backgroundColor(Color.Transparent).alignRules({left: {anchor: index.toString(), align: HorizontalAlign.Start},top: {anchor: index.toString(), align: VerticalAlign.Top},bottom: {anchor: index.toString(), align: VerticalAlign.Bottom},right: {anchor: index.toString(), align: HorizontalAlign.End}})}}.layoutWeight(1).height(40).margin({right:index == this.inputIndex.length-1?0:10})})
}

到这里,就解决了点击非焦点输入框的问题。

总结

看起来挺简单的,实际上一点也不难。
还有可以优化的地方:
比如输入的内容可以不用数组,直接用字符串就好,删除和添加都是在末尾进行
比如焦点也可以不用记录的,直接用输入的字符串长度来判断就好。

遗留下的一个问题:
在上面使用enable来控制是否可点击时,为什么先设置enable为true,然后请求焦点会报错?
先设置enable为true,在下一帧时请求焦点还是报错。
这就留给大佬翻源码解释了。


附一个完整代码

import { hilog } from '@kit.PerformanceAnalysisKit'
@Component
struct FourTextInput {onFinishInput?: (value: string) => void@State inputValue: string[] = ["", "", "", ""]@State inputEnable: boolean[] = [true, false, false, false]inputIndex: number[] = [0, 1, 2, 3]build() {Row(){ForEach(this.inputIndex,(index:number)=>{RelativeContainer(){TextInput({text:this.inputValue[index]}).layoutWeight(1).textInputStyle(this.inputEnable[index]).maxLength(1).maxLines(1).id(index.toString()).type(InputType.Number).onDidDelete((_)=>{hilog.error(0x01,"InputVerificationCode",`${index}个执行 onDidDelete`)if(this.inputValue[index].length == 0){//不是第一个输入框 且 输入框内没有文字,则删除上一个输入框内容,并且使上一个输入框获取焦点if(index !=0){this.inputValue[index-1]=""this.inputEnable[index] =falsethis.inputEnable[index-1] =truethis.getUIContext().getFocusController().requestFocus((index-1).toString())}else{//如果输入框内有文字,则只删除当前输入框内容this.inputValue[index]=""}}}).onChange((value: string, previewText?: PreviewText)=>{hilog.error(0x01,"InputVerificationCode",`${index}个onChange:  value:${value}  previewText: value-> ${previewText?.value}    offset->${previewText?.offset}`   )this.inputValue[index]= valueif(value.length == 1){if(index != 3){this.inputEnable[index+1] =truethis.getUIContext().getFocusController().requestFocus((index+1).toString())this.inputEnable[index] = false// this.inputEnable[index] =false}else{if(this.onFinishInput){let result = ""for(let i =0;i< this.inputValue.length;i++){result += this.inputValue[i]}this.onFinishInput(result)}}}})if(!this.inputEnable[index]){Text().backgroundColor(Color.Transparent).alignRules({left: {anchor: index.toString(), align: HorizontalAlign.Start},top: {anchor: index.toString(), align: VerticalAlign.Top},bottom: {anchor: index.toString(), align: VerticalAlign.Bottom},right: {anchor: index.toString(), align: HorizontalAlign.End}})}}.layoutWeight(1).height(40).margin({right:index == this.inputIndex.length-1?0:10})})}.onAppear(()=>{this.getUIContext().getFocusController().requestFocus("0")})}
}@Extend(TextInput)
function textInputStyle(enable: boolean) {.border({width: 1,color: enable ? "#1b91e0" : "#999999",radius: 4,style: BorderStyle.Solid,}).textAlign(TextAlign.Center).layoutWeight(1).maxLength(1).maxLines(1).type(InputType.Number).layoutWeight(1).height(40)
}export { FourTextInput }

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

谈谈对线程的认识

面对这样的一个多核CPU时代, 实现并发编程是刚需. 多进程实现并发编程, 效果是特别理想的. 但是, 多线程编程模型有一个明显的缺点, 就是进程太重了, 效率不高. 创建一个进程, 消耗时间比较多. 销毁一个进程, 消耗时间也比较多. 调度一个进程, 消耗时间也比较多. 这里的时…

MySQL的数据类型

4. 数据类型 4.1 数据类型分类4.2 数值类型4.2.1 tinyint类型4.2.2 bit类型4.2.3 小数类型4.2.3.1 float4.2.3.2 decimal 4.3 字符串类型4.3.1 char4.3.2 varchar4.3.3 char和varchar比较 4.4 日期和时间类型enum和set 4.1 数据类型分类 4.2 数值类型 4.2.1 tinyint类型 数值越…

回不去的乌托邦

回不去的乌托邦 坐在电脑面前愣神间已至深夜&#xff0c;依然睡意不起。 相比于带着疲惫入睡&#xff0c;伏案发呆更令人惬意。想起最近在自媒体上看到的一句话“最顶级的享受变成了回不去的乌托邦”。 “这是兄弟们最后一次逛校园了&#xff0c;我拍个照”。我的记忆力总是用在…

分布式与集群,二者区别是什么?

??分布式 分布式系统是由多个独立的计算机节点组成的系统&#xff0c;这些节点通过网络协作完成任务。每个节点都有自己的独立计算能力和存储能力&#xff0c;可以独立运行。分布式系统的目标是提高系统的可靠性、可扩展性和性能。 分布式服务包含的技术和理论 负载均衡&am…

<02.21>八股文

JAVA基础 次数少了用解释性 次数多了用编译性(JIT) 操作系统

logging-operator 部署fluentd-bit日志报kubernetes链接错误

一、背景&#xff1a; 某项目使用logging-operator部署fluentd-bit进行日志采集&#xff0c;发现启动的fluentd-bit有大量的的链接kubernetes报错。 二、排查过程 1、排查fluentd容器到kubernetes api server的联通性&#xff0c;进入容器中curl kubernetes.default.svc.local:…

Redis数据结构-String字符串

1.String字符串 字符串类型是Redis中最基础的数据结构&#xff0c;关于数据结构与要特别注意的是&#xff1a;首先Redis中所有的键的类型都是字符串类型&#xff0c;而且其他集中数据结构也都是在字符串类似基础上进行构建&#xff0c;例如列表和集合的元素类型是字符串类型&a…

基于Django的购物商城平台的设计与实现(源码+lw+部署文档+讲解),源码可白嫖!

摘要 当今社会进入了科技进步、经济社会快速发展的新时代。国际信息和学术交流也不断加强&#xff0c;计算机技术对经济社会发展和人民生活改善的影响也日益突出&#xff0c;人类的生存和思考方式也产生了变化。传统购物管理采取了人工的管理方法&#xff0c;但这种管理方法存…

Unity结合Vuforia虚拟按键实现AR机械仿真动画效果

零、最终效果 待上传 一、资源准备 1、Vuforia Vuforia版本不能高于10.17.4&#xff08;往上的版本虚拟按键功能被删除&#xff09; 2、Unity Unity版本必须要高于2022.3.x&#xff0c;不然使用Vuforia插件时会出现bug 二、主要内容 1、添加虚拟按钮 2、为虚拟按钮设置…

MATLAB在投资组合优化中的应用:从基础理论到实践

引言 投资组合优化是现代金融理论中的核心问题之一&#xff0c;旨在通过合理配置资产&#xff0c;实现风险与收益的最佳平衡。MATLAB凭借其强大的数学计算能力和丰富的金融工具箱&#xff0c;成为投资组合优化的理想工具。本文将详细介绍如何使用MATLAB进行投资组合优化&#…

Day15-后端Web实战-登录认证——会话技术JWT令牌过滤器拦截器

目录 登录认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 2. 登录校验2.1 问题分析2.2 会话技术2.2.1 会话技术介绍2.2.2 会话跟踪方案2.2.2.1 方案一 - Cookie2.2.2.2 方案二 - Session2.2.2.3 方案三 - 令牌技术 2.3 JWT令牌2.3.1 介绍2.3.2 生成和校…

【实战篇】【深度介绍 DeepSeek R1 本地/私有化部署大模型常见问题及解决方案】

引言 大家好&#xff01;今天我们来聊聊 DeepSeek R1 的本地/私有化部署大模型。如果你正在考虑或者已经开始了这个项目&#xff0c;那么这篇文章就是为你准备的。我们会详细探讨常见问题及其解决方案&#xff0c;帮助你更好地理解和解决在部署过程中可能遇到的挑战。准备好了…

大模型本地部署及本地知识库构建

1、引言 随着AI技术的快速发展和普及&#xff0c;越来越多的LLM开始开源&#xff0c;若想在本地尝试部署大模型和搭建知识库&#xff0c;可以使用ollamaLLMscherry Studio nomic-embed-text的框架来实现&#xff0c;以便于对AI简单应用流程的整体了解。本地部署和知识库的搭建…

在 Ansys Motion 中创建链式伸缩臂的分步指南

介绍 链传动在负载和/或运动要远距离传递的机器中非常多产&#xff0c;例如&#xff0c;在两个平行轴之间。链条驱动系统的设计需要了解载荷传递和运动学如何影响链条张力、轴轴承中的悬臂载荷、轴应力和运动质量等。使用 Ansys Motion&#xff0c;可以轻松回答上述所有问题以…

blender笔记2

一、物体贴地 物体->变换->对齐物体 ->对齐弹窗(对齐模式&#xff1a;反方&#xff0c;相对于&#xff1a;场景原点&#xff0c;对齐&#xff1a;z)。 之后可以设置原点->原点--3d游标 二、面上有阴影 在编辑模式下操作过后&#xff0c;物体面有阴影。 数据-&g…

SPRING10_SPRING的生命周期流程图

经过前面使用三大后置处理器BeanPostProcessor、BeanFactoryPostProcessor、InitializingBean对创建Bean流程中的干扰,梳理出SPRING的生命周期流程图如下

光子集成电路加速边缘AI推理:突破传统NPU的能效比极限

引言&#xff1a;边缘计算的能耗困局 某领先自动驾驶公司采用128核光子张量处理器后&#xff0c;激光雷达点云处理能效比达458TOPS/W&#xff0c;是传统车规级GPU方案的57倍。在16线束LiDAR实时语义分割任务中&#xff0c;光子矩阵乘法单元将特征提取延迟从8.3ms降至0.12ms&am…

【EndNote】WPS 导入EndNote 21

写在前面&#xff1a;有没有人有激活码&#xff0c;跪求&#xff01; EndNote&#xff0c;在文献管理和文献引用方面很好用。写文章的时候&#xff0c;使用EndNote引入需要的文献会很方便。我目前用的WPS&#xff0c;想把EndNote的CWYW&#xff08;Cite While You Write&#…

2025.2.23机器学习笔记:PINN文献阅读

2025.2.23周报 一、文献阅读题目信息摘要Abstract创新点网络架构架构A架构B架构C 实验结论后续展望 一、文献阅读 题目信息 题目&#xff1a; Physics-Informed Neural Networks for Modeling Water Flows in a River Channel期刊&#xff1a; IEEE TRANSACTIONS ON ARTIFICI…