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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 18:57:08