Jetpack Compose中如何用自定义Drawable实现PIN输入框?
自定义PIN输入框实现方案
核心思路
用隐藏的BasicTextField处理数字输入、键盘弹出/收起等底层逻辑,同时自定义Composable绘制PIN的视觉样式,将输入内容与圆形状态绑定,既保留系统输入组件的便捷性,又完全满足自定义UI需求。
完整代码实现
import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview @Composable fun CustomPinInput( pinLength: Int = 2, onPinComplete: (String) -> Unit ) { var pinValue by remember { mutableStateOf(TextFieldValue("")) } val focusRequester = remember { FocusRequester() } // 隐藏的输入框,负责处理输入逻辑与键盘交互 BasicTextField( value = pinValue, onValueChange = { newText -> val filteredText = newText.text.filter { it.isDigit() }.take(pinLength) pinValue = TextFieldValue(filteredText) if (filteredText.length == pinLength) { onPinComplete(filteredText) } }, keyboardType = KeyboardType.Number, modifier = Modifier .focusRequester(focusRequester) .focusable() .size(0.dp), // 隐藏输入框本体 singleLine = true ) // 自定义PIN视觉样式 Row { repeat(pinLength) { index -> val isFilled = index < pinValue.text.length val borderColor = if (isFilled) Color.Green else Color.Red androidx.compose.foundation.layout.Box( modifier = Modifier .size(48.dp) .clip(CircleShape) .drawBehind { // 绘制边框 drawCircle( color = borderColor, style = Stroke(width = 2.dp.toPx()) ) // 已输入时填充白色 if (isFilled) { drawCircle(color = Color.White) } } .padding(horizontal = 8.dp) ) } } // 自动获取焦点弹出键盘 androidx.compose.runtime.LaunchedEffect(Unit) { focusRequester.requestFocus() } } // 使用示例 @Preview @Composable fun PinInputDemo() { MaterialTheme { CustomPinInput(pinLength = 2) { pin -> // PIN输入完成后的业务逻辑 println("输入完成:$pin") } } }
关键细节说明
- 隐藏输入框的作用:无需自己处理键盘弹出、数字输入过滤、退格逻辑,完全复用
BasicTextField的成熟能力,只隐藏视觉部分。 - 状态绑定:通过
pinValue.text.length判断每个圆形的填充状态,已输入的位置显示绿边框白填充,未输入的显示红边框。 - 输入限制:自动过滤非数字字符,且输入长度不超过设定的PIN位数,避免无效输入。
- 自动聚焦:通过
FocusRequester在组件加载时自动获取焦点,直接弹出数字键盘,提升用户体验。
可扩展优化点
- 支持输入错误时的样式反馈(如边框闪烁、抖动动画)
- 开放圆形大小、边框宽度、颜色等配置参数
- 添加输入过程中的光标提示(比如当前输入位置的边框高亮)
内容的提问来源于stack exchange,提问作者Ralf Wickum
相关产品推荐
相关产品推荐

