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

Jetpack Compose价格Visual Transformation首字符0输入崩溃问题求解

解决Jetpack Compose价格Visual Transformation首字符0导致的崩溃问题

你的核心问题是:当输入以0开头(比如"0123"),DecimalFormat会自动移除前导0,导致格式化后的字符串长度短于原输入,进而让偏移量映射数组的索引越界,引发崩溃。以下是无需在onValueChange中转换数字的解决方案:

修改后的实现代码

class PriceTransformation(private val prefix: String = "₹ ") : VisualTransformation {

    // 定义价格格式化规则:##,##,###
    private val priceFormatter = DecimalFormat("##,##,###").apply {
        groupingSize = 3
        isParseBigDecimal = true
    }

    override fun filter(text: AnnotatedString): TransformedText {
        val originalText = text.text
        val originalLength = originalText.length

        // 处理前导0的特殊情况:如果原文本以0开头且长度>1,格式化时保留逻辑映射
        val formattedText = if (originalText.startsWith("0") && originalLength > 1) {
            // 先格式化去掉前导0后的内容,再把前导0加回去
            val trimmedText = originalText.trimStart('0').takeIf { it.isNotEmpty() } ?: "0"
            prefix + "0" + priceFormatter.format(trimmedText.toBigDecimal())
        } else {
            // 正常情况直接格式化,空输入默认显示₹ 0
            prefix + priceFormatter.format(originalText.toBigDecimalOrNull() ?: BigDecimal.ZERO)
        }

        val originalToTransformed = mutableListOf<Int>()
        val transformedToOriginal = mutableListOf<Int>()
        var specialCharsCount = 0
        var originalCharIndex = 0

        formattedText.forEachIndexed { transformedIndex, char ->
            when {
                // 前缀、空格、逗号属于特殊字符,不对应原文本数字
                char == '₹' || char == ' ' || char == ',' -> {
                    specialCharsCount++
                    transformedToOriginal.add(originalCharIndex)
                }
                // 处理手动添加的前导0,映射到原文本的第一个0
                formattedText.startsWith(prefix + "0") && transformedIndex == prefix.length -> {
                    originalToTransformed.add(transformedIndex)
                    transformedToOriginal.add(0)
                    originalCharIndex++
                }
                // 普通数字字符,按顺序对应原文本的字符
                else -> {
                    originalToTransformed.add(transformedIndex)
                    transformedToOriginal.add(originalCharIndex)
                    originalCharIndex++
                }
            }
        }

        // 补充末尾偏移量,避免越界
        originalToTransformed.add(
            originalToTransformed.lastOrNull()?.plus(1) ?: formattedText.length
        )
        transformedToOriginal.add(
            transformedToOriginal.lastOrNull()?.plus(1) ?: originalLength
        )

        val offsetMapping = object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                // 边界保护:偏移量超出数组长度时返回文本末尾
                return if (offset < originalToTransformed.size) {
                    originalToTransformed[offset]
                } else {
                    formattedText.length
                }
            }

            override fun transformedToOriginal(offset: Int): Int {
                // 边界保护:偏移量超出数组长度时返回原文本末尾
                return if (offset < transformedToOriginal.size) {
                    transformedToOriginal[offset]
                } else {
                    originalLength
                }
            }
        }

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

关键改动说明

  • 前导0保留逻辑:当原文本以0开头且长度大于1时,先剥离前导0进行格式化,再手动把0加回结果开头,既保证视觉上的前导0显示,又避免DecimalFormat自动移除它导致的长度不匹配。
  • 精准偏移映射:专门为手动添加的前导0建立映射关系,将其对应到原文本的第一个字符位置,确保光标移动逻辑正确。
  • 边界安全处理:在OffsetMapping的两个方法中添加边界判断,即使偏移量超出数组范围,也返回合法的文本末尾索引,彻底避免数组越界崩溃。
  • 空输入兼容:原文本为空时默认格式化显示₹ 0,避免转换异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 13:27:21