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")
    }
}

发现

Jetpack Compose实现 验证码输入框
太棒了,单个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 ->
     //开端大显神通
}

三种状况

Jetpack Compose实现 验证码输入框
能够发现验证码框框有三种状况

  • 表明验证码现已彻底输入
  • 表明验证码正在输入中,还未完结。
  • 表明验证码没有开端输入。
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 不收效

Jetpack Compose实现 验证码输入框
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
            )
        )
    }
}

效果图

Jetpack Compose实现 验证码输入框

Jetpack Compose实现 验证码输入框

完好代码

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