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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 14:20:57