Compose中TextField货币掩码问题排查与优化方案咨询
TextField货币掩码无法完全删除字符的问题排查与优化方案
问题说明
需要为TextField实现格式为$ NNNNNNNNNNNNNNNNNNNNN的掩码(前缀$ ,后跟最多20位数字),但当前代码存在无法完全删除输入字符的问题,需排查代码问题并提供可靠实现。
原代码
掩码转换类
class CurrencyMaskTransformation : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText = maskFilter(text) private fun maskFilter(text: AnnotatedString): TransformedText { // $ NNNNNNNNNNNNNNNNNNNNN var out = "" for (i in text.text.indices) { if (i == 0) out += "$ " out += text.text[i] } val numberOffsetTranslator = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { if (offset <= 0) return offset if (offset <= 2) return offset + 1 return text.text.length + 2 } override fun transformedToOriginal(offset: Int): Int { if (offset <= 0) return offset if (offset <= 2) return offset - 1 return text.text.length } } return TransformedText(AnnotatedString(out), numberOffsetTranslator) } }
TextField使用代码
val inputValue = remember { mutableStateOf(TextFieldValue("100")) } TextField( modifier = Modifier .wrapContentWidth() .height(64.dp), value = inputValue.value, onValueChange = { inputValue.value = it }, visualTransformation = CurrencyMaskTransformation() )
问题排查
- 掩码拼接逻辑错误:原代码仅在输入第一个字符时添加
$前缀,输入为空时无前缀显示;且未过滤非数字字符,易导致格式混乱。 - OffsetMapping逻辑严重错误:
originalToTransformed中,当offset > 2时直接返回text.text.length + 2,导致所有原偏移量大于2的位置都映射到掩码文本末尾,光标位置完全错位。transformedToOriginal中,当offset > 2时直接返回text.text.length,删除操作时无法正确映射到原文本对应位置,导致无法逐字符删除,最终出现删不干净的情况。
优化实现方案
步骤1:修正掩码转换类
实现正确的掩码拼接(始终显示$ 前缀,仅保留数字字符,限制最大20位),并编写正确的OffsetMapping:
class CurrencyMaskTransformation : VisualTransformation { // 前缀"$ "的长度 private val prefixLength = 2 // 最大允许输入的数字位数 private val maxDigitCount = 20 override fun filter(text: AnnotatedString): TransformedText { // 过滤非数字字符并限制长度 val cleanedText = text.text.filter { it.isDigit() }.take(maxDigitCount) // 拼接标准掩码格式 val maskedText = "$ $cleanedText" val offsetMapping = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { // 原文本的每个字符都对应掩码文本前缀后的位置,偏移量加前缀长度 return offset + prefixLength } override fun transformedToOriginal(offset: Int): Int { // 掩码前缀区域对应原文本起始位置,前缀后位置减去前缀长度得到原文本偏移量,同时限制不超过原文本长度 return maxOf(0, offset - prefixLength).coerceAtMost(cleanedText.length) } } return TransformedText(AnnotatedString(maskedText), offsetMapping) } }
步骤2:优化TextField输入处理
在onValueChange中提前过滤非数字字符,避免无效输入,同时修正光标位置:
val inputValue = remember { mutableStateOf(TextFieldValue("100")) } TextField( modifier = Modifier .wrapContentWidth() .height(64.dp), value = inputValue.value, onValueChange = { newValue -> // 过滤非数字字符并限制最大长度 val filteredText = newValue.text.filter { it.isDigit() }.take(20) // 修正光标位置,避免过滤后光标跳变 val newSelection = newValue.selection.copy( start = filteredText.length.coerceAtMost(newValue.selection.start), end = filteredText.length.coerceAtMost(newValue.selection.end) ) inputValue.value = TextFieldValue(filteredText, newSelection) }, visualTransformation = CurrencyMaskTransformation(), // 设置数字键盘,提升输入体验 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) )
方案说明
- 掩码逻辑:始终保留
$前缀,自动过滤非数字输入,严格限制20位数字长度,完全符合需求格式。 - OffsetMapping:精准映射原文本与掩码文本的光标位置,彻底解决删除异常问题。
- 输入过滤:提前拦截无效输入,配合数字键盘确保输入有效性,同时修正光标位置避免用户体验断层。
内容的提问来源于stack exchange,提问作者Master Disaster
相关产品推荐
相关产品推荐

