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

如何在Jetpack Compose中实现银行卡到期日期输入的格式化与校验

在Jetpack Compose中实现带验证的银行卡到期日期输入框

核心实现思路

  • 过滤输入内容:仅保留数字,限制最多4位长度
  • 实时验证月份:确保前两位为01-12,拦截无效输入
  • 验证过期年份:限制后两位不早于当前年份的最后两位
  • 结合已有的VisualTransformation实现MM/YY格式显示

完整代码实现

1. 自定义VisualTransformation(补充完整供参考)

class ExpiryDateVisualTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        val trimmedText = if (text.length >= 4) text.take(4) else text
        val formattedText = buildString {
            trimmedText.forEachIndexed { index, char ->
                append(char)
                // 在第2位后添加斜杠分隔符
                if (index == 1) append("/")
            }
        }

        val offsetMapping = object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                // 原字符串前2位对应格式化后的前2位,第3位及以后对应格式化后的第4位及以后(多了一个斜杠)
                return if (offset <= 1) offset else offset + 1
            }

            override fun transformedToOriginal(offset: Int): Int {
                // 格式化后的前3位(含斜杠)对应原字符串的前2位,第4位及以后对应原字符串的第3位及以后
                return if (offset <= 2) offset else offset - 1
            }
        }

        return TransformedText(AnnotatedString(formattedText), offsetMapping)
    }
}

2. 带验证逻辑的TextField实现

@Composable
fun ExpiryDateTextField() {
    var expiryDate by remember { mutableStateOf("") }
    val currentYearLastTwo = Calendar.getInstance().get(Calendar.YEAR) % 100
    val context = LocalContext.current

    // 验证月份有效性
    val isMonthValid = if (expiryDate.length >= 2) {
        expiryDate.substring(0, 2).toIntOrNull()?.let { it in 1..12 } ?: false
    } else true

    // 验证年份有效性
    val isYearValid = if (expiryDate.length == 4) {
        expiryDate.substring(2, 4).toIntOrNull()?.let { it >= currentYearLastTwo } ?: false
    } else true

    val isInputValid = isMonthValid && isYearValid

    TextField(
        value = expiryDate,
        onValueChange = { newInput ->
            // 过滤非数字字符,截取最多4位
            val filteredInput = newInput.filter { it.isDigit() }.take(4)
            var processedInput = filteredInput

            // 处理无效月份:强制修正为合法范围
            if (processedInput.length >= 2) {
                val monthPart = processedInput.substring(0, 2)
                val month = monthPart.toIntOrNull() ?: 0
                if (month !in 1..12) {
                    processedInput = when (processedInput[0]) {
                        // 第一位是1时,第二位只能是0-2,超出则强制设为2
                        '1' -> "1${if (processedInput[1] > '2') '2' else processedInput[1]}" + processedInput.substring(2)
                        // 第一位是0时,第二位不能是0,强制设为1-9
                        '0' -> "0${if (processedInput[1] == '0') '1' else processedInput[1]}" + processedInput.substring(2)
                        // 其他情况默认设为01
                        else -> "01" + processedInput.substring(2)
                    }
                }
            }

            // 处理过期年份:强制修正为当前年份并提示
            if (processedInput.length == 4) {
                val yearPart = processedInput.substring(2, 4)
                val year = yearPart.toIntOrNull() ?: 0
                if (year < currentYearLastTwo) {
                    processedInput = processedInput.substring(0, 2) + currentYearLastTwo.toString().padStart(2, '0')
                    Toast.makeText(context, "年份已过期,请输入有效年份", Toast.LENGTH_SHORT).show()
                }
            }

            expiryDate = processedInput
        },
        visualTransformation = ExpiryDateVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        placeholder = { Text(text = "MM/YY") },
        colors = TextFieldDefaults.textFieldColors(
            focusedIndicatorColor = if (isInputValid) Color.Blue else Color.Red,
            unfocusedIndicatorColor = if (isInputValid) Color.Gray else Color.Red,
            errorIndicatorColor = Color.Red
        ),
        singleLine = true,
        maxLines = 1
    )
}

关键逻辑说明

  • 输入过滤:通过filter { it.isDigit() }只保留数字,take(4)限制最大长度,避免多余输入。
  • 月份验证:当输入到第2位时,检查是否在1-12范围内,若无效则自动修正(比如输入13时改为12,输入00时改为01)。
  • 年份验证:输入满4位后,对比当前年份的最后两位,若输入年份更早则强制修正为当前年份并弹出提示。
  • 视觉反馈:通过TextField的指示器颜色变化,直观提示用户输入是否有效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 21:24:59