如何在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
相关产品推荐
相关产品推荐

