Jetpack Compose实现 验证码输入框

news2024/9/21 8:00:15

highlight: androidstudio

Jetpack Compose 作为 Android 的新一代 UI 开发框架,提供了非常强大的工具来构建用户界面。

今天,我们就利用 Compose 来实现一个炫酷的验证码输入框!

开始的思路是用6个TextField来实现

// 用于存储验证码的长度
val codeLength = 6
// 定义一个变量,用于存储验证码的值
val code = remember { mutableStateOf(TextFieldValue("\t\t\t\t\t\t")) }
// 定义一个变量,用于存储输入框的焦点请求器列表
val focusRequesters = remember { List(codeLength) { FocusRequester() } }
// 定义一个函数,用于处理输入框的文本变化事件
fun onTextChanged(text: String, index: Int) {
    // 更新验证码的值,将指定位置的字符替换为输入的文本
    code.value = code.value.copy(
        text = code.value.text.replaceRange(index, index + 1, text)
    )
    // 如果输入的文本不为空,并且不是最后一个输入框,那么请求下一个输入框获取焦点
    if (text.isNotEmpty() && index < codeLength - 1) {
        focusRequesters[index + 1].requestFocus()
    }else{
      //删除返回上一个
        val mIndex = if (index == 0) 0 else index - 1
        focusRequesters[mIndex].requestFocus()
        code.value.copy(selection = TextRange(1))
    }
}

后来发现自动切换焦点处理逻辑 不优雅
用1个TextField来实现才符合我的风格
后来突发奇想利用BasicTextFielddecorationBox试试

decorationBox的作用

Jetpack Compose 中的 BasicTextField 有一个 decorationBox 属性,它的作用是:可以使用自定义组件去装饰 BasicTextField
这样我们就可以:

  1. 自定义输入框的背景色、边框等样式。
  2. 添加前缀或后缀图标。
  3. 在输入框输入或获取焦点时具有过渡效果。
  4. 实现各种自定义输入框效果,比如我们实现的验证码输入框。
    用简单易懂的话来说decorationBox 属性给了我们大量定制 BasicTextField 样式和效果的自由度,我们可以根据界面需要构建出各式各样的输入框组件。

相比之下 OutlinedTextFieldTextField 等组件的定制空间就较小。所以,如果您要实现高度定制的输入框效果,BasicTextField 是一个很好的选择。

//省略亿些小细节
decorationBox = {
Row(){
    for (i in 0 until 6) {
         Text("9")
    }
}

发现
请添加图片描述

太棒了,单个TextField实现省去处理焦点的逻辑。

封装成简单易用的组件

@Composable
fun BaseVerificationCodeTextField(
    modifier: Modifier = Modifier,
    isError: Boolean = false,
    codeLength :Int = 6,
    fontSize : TextUnit = 21.sp,
    onVerify: (String) -> Unit = {},
    codeBox: @Composable RowScope.(codeLength: Int, index :Int ,code :String) -> Unit
) {
  1. 是否显示错误状态 isError
  2. codeLength 控制验证码的个数,这里默认是 6 个。
  3. fontSize 控制输入框内文本的大小
  4. onVerify 是一个回调函数,在完成所有输入框的输入时会被调用,参数是一个 String 表示最终输入的验证码。
  5. codeBox: 一个 composable 函数,用于自定义每个验证码框的样式。
    它有三个参数:
  • codeLength 验证码的个数,
  • index: 当前验证码框的索引,从 0 开始。
  • code:验证码
//存储文本输入的值
var text by remember { mutableStateOf("") }
//管理当前获得焦点的文本框
val focusManager = LocalFocusManager.current
//用于请求焦点以显示软键盘
val focusRequester = remember { FocusRequester() }
//它控制软键盘的显示和隐藏。
val keyboardController = LocalSoftwareKeyboardController.current

//获取焦点
LaunchedEffect(Unit) {
    focusRequester.requestFocus()
}

实现软键盘自动弹起

BasicTextField(
modifier = modifier
    .focusRequester(focusRequester)//监听焦点
    .onFocusChanged {
        if (it.isFocused) {
            //获取焦点后自动弹起软键盘
            keyboardController?.show()
        }
    })

decorationBox

Row(
    Modifier
        .background(Color.Transparent)
        .padding(horizontal = 12.dp),
    verticalAlignment = Alignment.CenterVertically,
    horizontalArrangement = Arrangement.spacedBy(6.dp)//可以控制子组件之间的距离
) {
    for (i in 0 until codeLength) {
        codeBox(i,text)
  }

onValueChange

文本框内容变化的回调函数 onValueChange

我们要 检查 输入的内容 的长度是否 <= codeLength(验证码的个数),且是否全部由数字组成。如果不满足,不做任何处理。

onValueChange = { newText ->
    // 限制最大长度为6且只能输入数字
    if (newText.length <= codeLength && newText.all { it.isDigit() }) {
        text = newText

        if (newText.length == codeLength) {
            //输入完成后自动提交并且隐藏软件盘
            onVerify(newText)
            focusManager.clearFocus()
        }
    }
},

仿百度验证码输入框

使用刚刚封装好的 BaseVerificationCodeTextField

BaseVerificationCodeTextField(
    onVerify = {
    //输入完成的回调
    }
) { codeLength, indxe, code ->
     //开始大显身手
}

三种状态

请添加图片描述

可以发现验证码框框有三种状态

  • 表示验证码已经完全输入
  • 表示验证码正在输入中,还未完成。
  • 表示验证码尚未开始输入。
private enum class CodeState {
    ENTERED,//表示验证码已经完全输入
    INPUTTING,//表示验证码正在输入中,还未完成。
    PENDING,//表示验证码尚未开始输入。
}

isHasCode 变量,表示当前索引的验证码位是否已经被输入。
计算方式是索引 index 小于 code 的长度,即用户输入的验证码字符串的长度。

val isHasCode = indxe < code.length
val fontSize = (144 / codeLength).sp

val codeState = when {
    isHasCode -> CodeState.ENTERED
    //如果 index 等于 code 长度,即用户输入到当前位
    (indxe == code.length) -> CodeState.INPUTTING
    else -> CodeState.PENDING
}

根据这三种状态定制各种效果

颜色

val cardColor = when (codeState) {
    CodeState.ENTERED -> Color(0xFF7593ff) //蓝色
    CodeState.INPUTTING -> Color.White //白色
    CodeState.PENDING -> Color(0xFFF5F5F5) //灰色
}

阴影高度

val elevation = when (codeState) {
    CodeState.ENTERED -> 3.dp
    CodeState.INPUTTING -> 6.dp
    CodeState.PENDING -> 0.dp
}
Card(
    Modifier
        .size((baseSize / codeLength).dp),
    colors = CardDefaults.cardColors(
        containerColor = cardColor
    ),
    elevation = CardDefaults.c(
        defaultElevation = elevation,
    ), 
) {

elevation 不生效

请添加图片描述
Cardelevation 不生效 但是Cardcolors生效了
我猜应该也许可能发生了这种情况:

  1. Card 设置了 elevation 和 colors, Compose 作用于这两个属性。
  2. 接着某个状态变化导致 Card 需要重新执行。
  3. Compose 首先会清除 Card 现有的 elevation 和 colors 效果。
  4. 接着 Compose 又作用于我们设置的新属性,应用新的 elevation 和 colors。
  5. 但此时,Card 的背景色已被清除,所以新的 elevation 设置就不会生效了。
  6. 最终,只有 colors 新设置的背景色生效了

Jetpack Compose 中的 Key 的作用

唯一标识 Compose 树中某个节点。
Compose 树某个节点的 Key 发生变化时,Compose 会将原节点与新节点进行比较,决定是否需要重新执行该节点。
简单来说,Key 的主要作用是提高 Compose 树的执行效率。通过 Key,Compose 可以精确判断哪些节点发生了变化,只需重新执行变化的节点,而保留那些 Key 未变化的节点。

正在输入时 _ 闪烁

val blinkInterval = 1000L //1秒闪烁一次
var isVisible by remember { mutableStateOf(true) }
LaunchedEffect(blinkInterval) {
    while (true) {
        isVisible = !isVisible
        delay(blinkInterval)
    }
}
CodeState.INPUTTING -> {
    if (isVisible) {
        Text(
            "_", style = TextStyle(
                fontSize = fontSize,
                color = textColor,
                textAlign = TextAlign.Center
            )
        )
    }
}

效果图

请添加图片描述
请添加图片描述

完整代码

BaseVerificationCodeTextField.kt

@ExperimentalComposeUiApi
@Composable
fun BaseVerificationCodeTextField(
    modifier: Modifier = Modifier,
    codeLength: Int = 6,
    onVerify: (String) -> Unit = {},
    codeBox: @Composable RowScope.(codeLength: Int, index: Int, code: String) -> Unit
) {

    //存储文本输入的值
    var text by remember { mutableStateOf("") }
    //管理当前获得焦点的文本框
    val focusManager = LocalFocusManager.current
    //用于请求焦点以显示软键盘
    val focusRequester = remember { FocusRequester() }
    //它控制软键盘的显示和隐藏。
    val keyboardController = LocalSoftwareKeyboardController.current

    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
    BasicTextField(
        value = text,
        singleLine = true,
        onValueChange = { newText ->
            // 限制最大长度为6且只能输入数字
            if (newText.length <= codeLength && newText.all { it.isDigit() }) {
                text = newText

                if (newText.length == codeLength) {
                    onVerify(newText)
                    focusManager.clearFocus()
                }
            }
        },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Number,
            imeAction = ImeAction.Done
        ),
        modifier = modifier
            .padding(horizontal = 26.dp)
            .fillMaxWidth()
            .focusRequester(focusRequester)
            .onFocusChanged {
                if (it.isFocused) {
                    keyboardController?.show()
                }
            }
            .wrapContentHeight(),
        readOnly = false,
        decorationBox = {
            Row(
                Modifier
                    .fillMaxWidth()
                    .background(Color.Transparent),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.SpaceAround
            ) {
                for (i in 0 until codeLength) {
                    codeBox(codeLength, i, text)

                }
            }
        }
    )
}

VerificationCodeTextField.kt

private enum class CodeState {
    ENTERED,//表示验证码已经完全输入
    INPUTTING,//表示验证码正在输入中,还未完成。
    PENDING,//表示验证码尚未开始输入。
}


@ExperimentalComposeUiApi
@Composable
fun VerificationCodeTextField(
    modifier: Modifier = Modifier,
    onVerify: (String) -> Unit = {}
) {

    val baseSize = 276
    BaseVerificationCodeTextField(
        onVerify = onVerify
    ) { codeLength, indxe, code ->
        // 判断当前位置是否有字符
        val isHasCode = indxe < code.length
        val fontSize = (144 / codeLength).sp

        val codeState = when {
            isHasCode -> CodeState.ENTERED
            (indxe == code.length) -> CodeState.INPUTTING
            else -> CodeState.PENDING
        }
        val cardColor = when (codeState) {
            CodeState.ENTERED -> Color(0xFF466Eff)
            CodeState.INPUTTING -> Color.White
            CodeState.PENDING -> Color(0xFFF5F5F5)
        }
        val elevation = when (codeState) {
            CodeState.ENTERED -> 3.dp
            CodeState.INPUTTING -> 6.dp
            CodeState.PENDING -> 0.dp
        }
        val textColor = when (codeState) {
            CodeState.ENTERED -> Color.White
            CodeState.INPUTTING -> Color.Gray
            CodeState.PENDING -> Color.Gray
        }

        val blinkInterval = 1000L
        var isVisible by remember { mutableStateOf(true) }
        LaunchedEffect(blinkInterval) {
            while (true) {
                isVisible = !isVisible
                delay(blinkInterval)
            }
        }
        key(elevation) {
            Card(
                Modifier
                    .size((baseSize / codeLength).dp),
                colors = CardDefaults.cardColors(
                    containerColor = cardColor
                ),
                elevation = CardDefaults.cardElevation(
                    defaultElevation = elevation,
                ),
            ) {
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = Alignment.Center
                ) {
                    when (codeState) {
                        CodeState.ENTERED -> {
                            Text(
                                code[indxe].toString(), style = TextStyle(
                                    fontSize = fontSize,
                                    color = textColor,
                                    textAlign = TextAlign.Center
                                )
                            )
                        }

                        CodeState.INPUTTING -> {
                            if (isVisible) {
                                Text(
                                    "_", style = TextStyle(
                                        fontSize = fontSize,
                                        color = textColor,
                                        textAlign = TextAlign.Center
                                    )
                                )
                            }
                        }

                        CodeState.PENDING -> {
                        // Text(
                        //            "_", style = TextStyle(
                        //                fontSize = fontSize,
                        //                color = textColor,
                        //                 textAlign = TextAlign.Center
                        //            )
                        //        )
                        }
                    }
                }
            }
        }
    }
}

使用方法

Column(Modifier.background(WordsFairyTheme.colors.whiteBackground)) {
    Spacer(modifier = Modifier.height(100.dp))

    VerificationCodeTextField(Modifier){
        ToastModel("验证码:$it").showToast()//
    }
}

ToastModel 这篇文章有描述
Jetpack Compose实现的一个优雅的 Toast

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

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

相关文章

Cesium 实战 - 使用 gltf-vscode 查看、预览以及编辑 glTF 和 GLB 模型

Cesium 实战 - 使用 gltf-vscode 查看、预览以及编辑 glTF 和 GLB 模型 VScode&#xff08;Visual Studio Code&#xff09; 安装模型必要插件VScode 预览自定义关节&#xff08;articulations&#xff09;动作VScode 导入 GLB 格式模型VScode 导出 GLB 格式模型 模型渲染作为 …

【什么是iMessage苹果推】怎样来获取设备令牌(Device Token)实现步骤

要获取设备令牌&#xff08;Device Token&#xff09;&#xff0c;您需要在应用程序中实现以下步骤&#xff1a; 在应用程序中请求用户授权&#xff1a;您需要请求用户授权允许应用程序发送远程通知。这可以通过使用 UNUserNotificationCenter&#xff08;User Notifications …

Linux学习之以openresty为例学习源码安装软件

https://github.com/openresty/openresty/tags里边有openresty各个版本的源码。 https://openresty.org/en/是官网。 wget https://github.com/openresty/openresty/archive/refs/tags/v1.15.8.1.tar.gz(github网址)或者wget https://openresty.org/download/openresty-1.15.…

6月29日第壹简报,星期四,农历五月十二

6月29日第壹简报&#xff0c;星期四&#xff0c;农历五月十二&#xff0c;早安&#xff01;坚持阅读&#xff0c;静待花开1. 中国移动元宇宙产业联盟成立&#xff0c;科大讯飞、华为、小米等为首批成员。2. 离岸人民币兑美元跌破7.25关口&#xff0c;创去年11月末来低位。3. 成…

STC89C52与LCD1602液晶显示的软硬件仿真

STC89C52与LCD1602液晶显示的软硬件仿真 硬件仿真平台&#xff1a;protues8.13 软件仿真平台&#xff1a;keil5 硬件连接图&#xff1a; 软件代码实现&#xff1a; &#xff08;复制后 粘贴到keil5中&#xff0c;即可使用&#xff0c;无需修改&#xff09; #include <RE…

SpringCloud-Nacos注册中心

文章目录 Nacos注册中心服务注册到nacos1&#xff09;引入依赖2&#xff09;配置nacos地址3&#xff09;重启 5.3.服务分级存储模型给user-service配置集群同集群优先的负载均衡 权重配置环境隔离创建namespace给微服务配置namespace Nacos与Eureka的区别 Nacos注册中心 服务注…

v8-tc39-ecma262: at,代替“arr[0]“取值

首先是语义化 其次是函数式&#xff0c;意味着加入流式调用队列 如上图&#xff0c;解释如下&#xff1a; 对象&#xff0c;调用对象函数处理类数组&#xff0c;调用类数组处理关联下标&#xff1f;转为Integer或者Infinity类型如果下标的值大于等于0&#xff0c;则设置赋值给…

Windows 驱动开发环境搭建

Windows 驱动开发环境搭建及 windbg 调试工具安装使用 引言了解 Windows 驱动开发环境下载 Windows 驱动开发环境根据需要下载安装对应版本的 Visual Studio下载安装对应的 WDK 工具包 编写第一个驱动代码总结参考资料 引言 对于 Windows 驱动开发&#xff0c;在微软官方的文档…

go定时任务crontab

在linux里可以通过crontab -e或者vi /etc/crontab编辑定时任务&#xff0c;区别在于后者只有root用户可以&#xff0c;还可以指定shell环境&#xff0c;不建议修改&#xff0c;修改前建议备份&#xff0c;前者任何用户都可以使用&#xff0c;两者修改后都不用修改自动重启。 1…

尚无忧宠物托运小程序app源码前景如何?

宠物托运市场调研分析 由于宠物托运在交通运输中并不是一个很大的类目&#xff0c;行业尚缺乏标准的流程规范与相关的监管机制&#xff0c;目前我国市面上常见的三方宠物托运公司多无正规手续&#xff0c;更有多数公司不具备相关运输资质。 如今&#xff0c;宠物经济不断崛起…

Linux:安装tomcat

注意&#xff1a;1.安装tomcat时最好用非root用户安装 2.可以选择新建一个用户&#xff0c;用户安装部署tomcat&#xff0c;本文将继续用fovace账户进行tomcat安装 一、前置条件 安装tomcat需要先安装jdk&#xff0c;所以先确定系统中是否已经有jdk&#xff0c;如下&#xff1a…

Minecraft-生成运行Spigot服务端

一、安装 先下载一个.jar的服务端核心&#xff0c;选择自己需要的版本 spigot核心下载 二、配置 下载完后&#xff0c;创建一个.bat批处理文件 内容填写如下&#xff0c;xxx.jar是你下载的核心名称 -Xms1G表示服务器所使用的最低运行内存为1G -Xmx1G表示服务器所使用的最高运行…

力扣 113. 路径总和 II

题目来源&#xff1a;https://leetcode.cn/problems/path-sum-ii/description/ C题解&#xff1a;采用递归法&#xff0c;前序遍历&#xff0c;遍历每个叶子节点&#xff0c;路径和满足条件则将该路径保存下来。 class Solution { public:void getlujing(TreeNode* node, int …

【MySQL】表中插入数据时,查询时,中文数据变成??

解决办法&#xff1a; 重新创建一个表&#xff08;users&#xff09; 将字符集那栏勾选上

[Windows] 电脑专属后花园 HideUL软件隐藏工具v1.0便携版

如下图所示:这一堆乱七八糟的东西,看起来又特难受…… 下载:https://download.csdn.net/download/mo3408/87961003 更有甚者,如果我们在电脑安装了一些比较特殊且不方便被别人看到的软件,也是一件麻烦事。 所以,今天我就给大家推荐一款可以隐藏电脑软件的小工具,仅需简…

【致敬未来的攻城狮计划】打卡4:检测按键

按键模块 后台轮询 按键也是一个比较简单的模块&#xff0c;主要是为了学习IO输入模式。 查看RA2E1电路图可见&#xff1a; 按键相关引脚是004引脚&#xff0c;默认上拉高电平&#xff0c;按下接地为低电平。 首先第一步还是设置对应引脚。类似上一期设置LED的方式&#xf…

【电磁泄密】网络杂谈(2)之电磁泄密及防护

涉及知识点 什么是电磁泄密&#xff0c;电磁泄密的渠道&#xff0c;电磁泄密该如何去防护&#xff0c;电磁泄密的防护标准。深入了解电磁泄密防护手段。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不胜感激 文章目录 …

内存分区.

内存模型分区&#xff1a; ****代码区&#xff1a;存放CPU执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它)&#xff0c;使其可共享的目的是对于频繁被执行的程序&#xff0c;只需要在内存中有一份代码即可。代码区通常楚只读的&#xff0c;使其只读的原因是…

NXP i.MX 8M Plus工业开发板硬件说明书( 四核ARM Cortex-A53 + 单核ARM Cortex-M7,主频1.6GHz)

前 言 本文主要介绍创龙科技TLIMX8MP-EVM评估板硬件接口资源以及设计注意事项等内容。 创龙科技TLIMX8MP-EVM是一款基于NXP i.MX 8M Plus的四核ARM Cortex-A53 单核ARM Cortex-M7异构多核处理器设计的高性能工业评估板&#xff0c;由核心板和评估底板组成。ARM Cortex-A53(…

VR全景如何保存本地,一个按钮即可解决

导语&#xff1a; 对于许多用户来说&#xff0c;保存VR全景作品到本地常常是一项繁琐而费时的任务。然而&#xff0c;现在有了蛙色3DVR平台&#xff0c;一切变得简单轻松&#xff0c;让您享受到便捷的离线导出体验。 平台为用户提供了专业的离线导出功能&#xff0c;使您能够轻…