You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现字母卡片式自定义TextField?

在Jetpack Compose中实现字母卡片式输入框

当然可以实现这种每个输入字母对应独立卡片、自动跳转焦点的效果,不需要依赖官方特殊组件,通过Compose基础API就能完成。先看你提到的效果示意图:

每个字母对应独立卡片的输入效果

核心实现思路

  • 状态管理:用状态列表存储每个卡片的输入内容,同时跟踪当前活跃的卡片索引。
  • 布局与样式:横向排列自定义卡片容器,每个容器内嵌入轻量输入组件,限制单字符输入。
  • 自动焦点跳转:利用FocusRequester管理每个输入框的焦点,输入完成后自动切换到下一个卡片。

完整代码示例

@Composable
fun CardTextField(count: Int = 4) {
    // 存储每个卡片的输入内容
    val inputs = remember { mutableStateListOf<String>().apply { repeat(count) { add("") } } }
    // 每个卡片对应的焦点请求器
    val focusRequesters = remember { List(count) { FocusRequester() } }
    // 当前活跃的卡片索引
    var currentIndex by remember { mutableIntStateOf(0) }

    Row(horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.padding(16.dp)) {
        repeat(count) { index ->
            val isFocused = currentIndex == index
            // 自定义卡片容器
            Box(
                modifier = Modifier
                    .size(60.dp)
                    .focusRequester(focusRequesters[index])
                    .border(
                        width = 2.dp,
                        color = if (isFocused) Color(0xFF2196F3) else Color.Gray,
                        shape = RoundedCornerShape(8.dp)
                    ),
                contentAlignment = Alignment.Center
            ) {
                BasicTextField(
                    value = inputs[index],
                    onValueChange = { newValue ->
                        if (newValue.length <= 1) {
                            inputs[index] = newValue
                            // 输入完成且不是最后一个卡片时,跳转到下一个
                            if (newValue.isNotEmpty() && index < count - 1) {
                                currentIndex = index + 1
                                focusRequesters[index + 1].requestFocus()
                            }
                        }
                    },
                    modifier = Modifier.fillMaxSize(),
                    singleLine = true,
                    textStyle = TextStyle(fontSize = 24.sp, textAlign = TextAlign.Center),
                    keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
                    decorationBox = { innerTextField ->
                        // 自定义输入框显示逻辑
                        when {
                            isFocused && inputs[index].isEmpty() -> innerTextField() // 显示光标
                            inputs[index].isNotEmpty() -> Text(
                                text = inputs[index],
                                style = TextStyle(fontSize = 24.sp)
                            )
                            else -> Text(
                                text = "_",
                                style = TextStyle(fontSize = 24.sp, color = Color.Gray)
                            )
                        }
                    }
                )
            }
        }
    }

    // 初始请求第一个卡片的焦点
    LaunchedEffect(Unit) {
        focusRequesters[0].requestFocus()
    }
}

关键细节说明

  • BasicTextField的优势:相比TextField,它允许完全自定义输入框的装饰逻辑,比如隐藏默认边框、自定义空状态提示。
  • 焦点控制:FocusRequester确保输入完成后焦点能准确切换到下一个卡片,提升用户体验。
  • 扩展性:可以轻松修改卡片数量、样式(比如调整圆角、边框色),或者添加输入限制(比如仅允许数字、大写字母)。

内容的提问来源于stack exchange,提问作者voodookiidoo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 11:06:30