Jetpack Compose支持小数的千位分隔符Visual Transformation实现
实现支持小数的千位分隔符Visual Transformation
下面是适配小数输入场景的千位分隔符Visual Transformation实现,它会自动给整数部分添加千分位分隔,同时限制小数点最多出现一次:
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation class DecimalThousandsSeparatorVisualTransformation : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { // 清理输入:只保留数字和单个小数点 val cleanedInput = text.text.filter { it.isDigit() || it == '.' } .let { rawText -> if (rawText.count { it == '.' } > 1) { val firstDotIndex = rawText.indexOf('.') rawText.substring(0, firstDotIndex + 1) + rawText.substring(firstDotIndex + 1).filter { it.isDigit() } } else rawText } // 拆分整数与小数部分 val numberParts = cleanedInput.split('.') val integerSegment = numberParts.getOrNull(0) ?: "" val decimalSegment = numberParts.getOrNull(1) ?: "" // 给整数部分添加千位分隔符 val formattedInteger = if (integerSegment.isNotEmpty()) { integerSegment.reversed().chunked(3).joinToString(",").reversed() } else "" // 拼接最终显示文本 val transformedDisplayText = if (decimalSegment.isNotEmpty()) { "$formattedInteger.$decimalSegment" } else formattedInteger // 构建光标偏移映射,确保光标位置正确 val offsetMapper = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { val originalSubText = cleanedInput.take(offset) val integerLength = originalSubText.split('.').first().length val separatorCount = if (integerLength > 0) (integerLength - 1) / 3 else 0 return offset + separatorCount } override fun transformedToOriginal(offset: Int): Int { val transformedSubText = transformedDisplayText.take(offset) val separatorCount = transformedSubText.count { it == ',' } return offset - separatorCount } } return TransformedText(AnnotatedString(transformedDisplayText), offsetMapper) } }
使用示例
在Compose TextField中直接引用该实现即可:
TextField( value = inputValue, onValueChange = { newInput -> // 额外做输入校验,避免无效内容 inputValue = newInput.filter { it.isDigit() || it == '.' } .let { s -> if (s.count { it == '.' } > 1) { val firstDotPos = s.indexOf('.') s.substring(0, firstDotPos + 1) + s.substring(firstDotPos + 1).filter { it.isDigit() } } else s } }, visualTransformation = DecimalThousandsSeparatorVisualTransformation(), label = { Text("输入带千分位的小数") } )
核心逻辑说明
- 输入清洗:过滤非数字/小数点字符,强制限制小数点仅出现一次
- 千分位处理:仅对整数部分做分隔,小数部分保持原始输入(符合常规数字展示习惯)
- 光标校正:通过偏移映射处理分隔符带来的文本长度变化,保证光标始终处于正确位置
内容的提问来源于stack exchange,提问作者Reza Faraji
相关产品推荐
相关产品推荐

