Jetpack 组成千位分隔符视觉转换,也适用于小数

Rez*_*aji 3 android kotlin android-jetpack-compose android-compose-textfield

如何实现千位分隔符视觉转换,该转换也适用于小数。我找到了 Int 数字的千位分隔符视觉转换的实现,但问题是当我想将它用于十进制数字时,我必须控制小数分隔符的计数不超过 1 次。

实施链接

Gab*_*tti 6

您可以使用:

  • onValueChange使用正则表达式模式将允许的字符限制为十进制数的属性
  • visualTransformation使用千位分隔符格式化数字

就像是:

val pattern = remember { Regex("^\\d*\\.?\\d*\$") }

TextField(
    value = text,
    onValueChange = {
        if (it.isEmpty() || it.matches(pattern)) {
            text = it
        }
    },
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
    visualTransformation = ThousandSeparatorTransformation()
)

class ThousandSeparatorTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {

        val symbols = DecimalFormat().decimalFormatSymbols
        val decimalSeparator = symbols.decimalSeparator

        var outputText = ""
        var integerPart = 0L
        var decimalPart = ""

        if (text.text.isNotEmpty()) {
            val number = text.text.toDouble()
            integerPart = number.toLong()
            outputText += NumberFormat.getIntegerInstance().format(integerPart)
            if (text.text.contains(decimalSeparator)) {
                decimalPart = text.text.substring(text.text.indexOf(decimalSeparator))
                if (decimalPart.isNotEmpty()) {
                    outputText += decimalPart
                }
            }
        }

        val numberOffsetTranslator = object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                return outputText.length
            }

            override fun transformedToOriginal(offset: Int): Int {
                return text.length
            }
        }

        return TransformedText(
            text = AnnotatedString(outputText),
            offsetMapping = numberOffsetTranslator
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,OffsetMapping光标将保持静止在值的末尾。否则,您必须计算数千个分隔符计数并根据它修复偏移量。

在此输入图像描述

  • 明白了,供将来参考,如果您想启用光标拖动,这是代码片段。只需减去它的倒数即可。`offset + countThousandSeparators(outputText.take(offset + 1))` (2认同)

Sar*_*mov 5

Decimal Amount Visual Transformation - Jetpack 为十进制输入编写视觉转换. 光标工作得很好!

小数金额转换

private val groupingSymbol = ' '
private val decimalSymbol = '.'

private val numberFormatter: DecimalFormat = DecimalFormat("#,###").apply {
    decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault()).apply {
        groupingSeparator = groupingSymbol
        decimalSeparator = decimalSymbol
    }
}

class DecimalAmountTransformation : VisualTransformation {

    override fun filter(text: AnnotatedString): TransformedText {
        val transformation = reformat(text.text)

        return TransformedText(
            AnnotatedString(transformation.formatted ?: ""),
            object : OffsetMapping {
                override fun originalToTransformed(offset: Int): Int {
                    return transformation.originalToTransformed[offset]
                }

                override fun transformedToOriginal(offset: Int): Int {
                    return transformation.transformedToOriginal[offset]
                }
            },
        )
    }

    private fun reformat(original: String): Transformation {
        val parts = original.split(decimalSymbol)
        check(parts.size < 3) { "original text must have only one dot (use filteredDecimalText)" }

        val hasEndDot = original.endsWith('.')
        var formatted = original

        Log.d("original_tag", original)

        if (original.isNotEmpty() && parts.size == 1) {
            formatted = numberFormatter.format(BigDecimal(parts[0]))

            if (hasEndDot) {
                formatted += decimalSymbol
            }
        } else if (parts.size == 2) {
            val numberPart = numberFormatter.format(BigDecimal(parts[0]))
            val decimalPart = parts[1]

            formatted = "$numberPart.$decimalPart"
        }

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

        formatted.forEachIndexed { index, char ->
            if (groupingSymbol == char) {
                specialCharsCount++
            } else {
                originalToTransformed.add(index)
            }
            transformedToOriginal.add(index - specialCharsCount)
        }
        originalToTransformed.add(originalToTransformed.maxOrNull()?.plus(1) ?: 0)
        transformedToOriginal.add(transformedToOriginal.maxOrNull()?.plus(1) ?: 0)

        return Transformation(formatted, originalToTransformed, transformedToOriginal)
    }
}


data class Transformation(
    val formatted: String?,
    val originalToTransformed: List<Int>,
    val transformedToOriginal: List<Int>,
)
Run Code Online (Sandbox Code Playgroud)

我们还需要过滤输入以获得所需的结果 DecimalnputFilter.kt

private val decimalSymbol = '.'

object InputFilterRegex {
    val DecimalInput by lazy { Regex("^(\\d*\\.?)+\$") }
}


fun filteredDecimalText(input: TextFieldValue): TextFieldValue {
    var inputText = input.text.replaceFirst(regex = Regex("^0+(?!$)"), "")
    var startsWithDot = input.text.startsWith(decimalSymbol)

    var selectionStart = input.selection.start
    var selectionEnd = input.selection.end

    if (startsWithDot) {
        inputText = "0$inputText"

        if (selectionStart == selectionEnd) {
            selectionStart++
            selectionEnd++
        } else {
            selectionEnd++
        }
    }

    val parts = inputText.split(decimalSymbol)
    var text = if (parts.size > 1) {
        parts[0] + decimalSymbol + parts.subList(1, parts.size).joinToString("")
    } else {
        parts.joinToString("")
    }

    if (text.startsWith(decimalSymbol)) {
        text = "0$text"
    }

    return input.copy(text = text, selection = TextRange(selectionStart, selectionEnd))
}
Run Code Online (Sandbox Code Playgroud)

最后,用法将如下所示:

BasicTextField(
    value = value,
    onValueChange = {
        if (!it.text.contains(InputFilterRegex.DecimalInput)) {
            return@BasicTextField
        }
        onValueChange(filteredDecimalText(it))
    },
    visualTransformation = DecimalAmountTransformation(),
)
Run Code Online (Sandbox Code Playgroud)

要点在这里