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

Kotlin如何实现EditText实时按###.###,##规则格式化数字

Android Kotlin 实现EditText实时数字格式化(格式:###.###,##)

规则说明

网上现有方案不生效的核心原因是没有匹配该格式的动态分隔符逻辑,先明确规则:

  • 自动过滤所有非数字输入,仅保留用户输入的纯数字内容
  • 输入内容的最后2位固定为小数位
  • 剩余前部内容为整数部分,从右往左每3位拆分为一个数字组
  • 整数组数量为1时,整数和小数之间用.分隔;整数组数量≥2时,整数组之间用.做千分分隔,最右侧整数组和小数之间用,分隔
  • 输入纯数字长度不足2位时,整数位自动补0,完全匹配给定示例:
    • 输入23 → 整数组为[0],小数为23 → 输出0.23
    • 输入11123456 → 整数组为[111,234],小数为56 → 输出111.234,56

实现代码

通过TextWatcher实现,规避递归触发、光标错位问题,删除/输入内容时格式自动适配:

import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText

fun EditText.addRealtimeNumberFormatter() {
    val watcher = object : TextWatcher {
        private var isProcessing = false
        private var lastOutput = ""

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}

        override fun afterTextChanged(editable: Editable?) {
            if (isProcessing) return
            isProcessing = true

            // 提取纯数字,过滤所有非数字字符
            val rawDigits = editable.toString().filter { it.isDigit() }
            if (rawDigits.isEmpty()) {
                setText("")
                setSelection(0)
                isProcessing = false
                return
            }

            // 补前导0,保证至少2位数字对应小数部分
            val paddedDigits = rawDigits.padStart(2, '0')
            // 拆分整数、小数部分
            val decimalPart = paddedDigits.takeLast(2)
            val integerRaw = paddedDigits.dropLast(2).ifEmpty { "0" }

            // 整数部分从右往左每3位拆分为一组
            val integerGroups = StringBuilder(integerRaw)
                .reverse()
                .toString()
                .chunked(3)
                .map { it.reversed() }
                .reversed()

            // 按分组数量拼接最终格式
            val result = if (integerGroups.size == 1) {
                "${integerGroups[0]}.$decimalPart"
            } else {
                "${integerGroups.joinToString(".")},$decimalPart"
            }

            // 避免重复设置相同文本
            if (result == lastOutput) {
                isProcessing = false
                return
            }
            lastOutput = result

            // 计算光标偏移量,保证光标跟随输入位置不跳转
            val originalCursorPos = selectionStart
            val digitsBeforeCursor = editable.toString()
                .substring(0, originalCursorPos.coerceAtMost(editable.length))
                .count { it.isDigit() }
            var newCursorPos = 0
            var countedDigits = 0
            for ((idx, c) in result.withIndex()) {
                if (c.isDigit()) countedDigits++
                if (countedDigits == digitsBeforeCursor) {
                    newCursorPos = idx + 1
                    break
                }
            }
            newCursorPos = newCursorPos.coerceIn(0, result.length)

            setText(result)
            setSelection(newCursorPos)
            isProcessing = false
        }
    }
    addTextChangedListener(watcher)
}

使用方式

直接给目标EditText调用扩展函数即可,建议同时指定数字键盘优化输入体验:

etAmount.addRealtimeNumberFormatter()
etAmount.inputType = android.text.InputType.TYPE_CLASS_NUMBER

边界场景验证

  • 输入2 → 输出0.02
  • 输入23 → 输出0.23
  • 输入123 → 输出1.23
  • 输入12345 → 输出123.45
  • 输入123456 → 输出1.234,56
  • 输入11123456 → 输出111.234,56
  • 输入1234567890 → 输出12.345.678,90
  • 删除内容时格式自动回退,无分隔符残留、光标错位问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:31:06