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

Kotlin实现EditText实时小数点分隔及计算结果格式化

税务计算程序:EditText实时数字格式化与结果显示优化

一、实现EditText输入时实时格式化(逗号为小数点)

创建自定义TextWatcher,处理输入文本的格式化,同时保持光标位置正确,避免格式化后光标错位:

private class DecimalTextWatcher(private val editText: EditText) : TextWatcher {
    private var isFormatting = false
    private val decimalFormat: DecimalFormat

    init {
        val symbols = DecimalFormatSymbols(Locale("pt", "Brazil"))
        symbols.decimalSeparator = ','
        symbols.groupingSeparator = '.'
        decimalFormat = DecimalFormat("###,###,###,###.####", symbols)
        decimalFormat.isParseBigDecimal = true
    }

    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(s: Editable?) {
        if (isFormatting || s.isNullOrEmpty()) return
        isFormatting = true

        // 清理输入:只保留数字和单个逗号
        val cleanText = s.toString().replace("[^\\d,]".toRegex(), "")
        val singleCommaText = cleanText.replace(",+".toRegex(), ",")

        // 限制小数位最多4位
        val parts = singleCommaText.split(",")
        val processedText = if (parts.size > 1) {
            val decimalPart = parts[1].take(4)
            "${parts[0]},$decimalPart"
        } else {
            singleCommaText
        }

        // 格式化千位分隔符
        val formattedText = try {
            val number = if (processedText.contains(',')) {
                processedText.replace(',', '.').toBigDecimal()
            } else {
                processedText.toBigDecimal()
            }
            decimalFormat.format(number)
        } catch (e: NumberFormatException) {
            processedText
        }

        // 保持光标位置正确
        val cursorPosition = editText.selectionStart
        val originalLength = s.length
        val newLength = formattedText.length
        val cursorOffset = if (cursorPosition < originalLength && originalLength > 0 && newLength > 0) {
            val diff = newLength - originalLength
            cursorPosition + diff
        } else {
            newLength
        }

        editText.setText(formattedText)
        editText.setSelection(cursorOffset.coerceAtMost(formattedText.length))

        isFormatting = false
    }
}

给每个EditText绑定该Watcher:

proveitos.addTextChangedListener(DecimalTextWatcher(proveitos))
custos.addTextChangedListener(DecimalTextWatcher(custos))
amortizacoes.addTextChangedListener(DecimalTextWatcher(amortizacoes))
juros.addTextChangedListener(DecimalTextWatcher(juros))

二、修正计算结果的显示逻辑

  1. 转换EditText文本为数字时,需将逗号替换为点(Kotlin数字转换默认识别点为小数点):
val prov = proveitos.text.toString().replace(',', '.').toFloat()
val cust = custos.text.toString().replace(',', '.').toFloat()
val amort = amortizacoes.text.toString().replace(',', '.').toFloat()
val jur = juros.text.toString().replace(',', '.').toFloat()
  1. 使用格式化后的结果更新TextView:
// 替换原代码中imposto.setText(result.toString())
imposto.setText(formatResult)

三、完整修改后的代码

package com.marianomuendane.contabiliadadeVocacional

import android.content.res.Configuration
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.math.BigDecimal
import java.util.Locale

class IRPCFragment : Fragment() {

    private lateinit var proveitos: EditText
    private lateinit var custos: EditText
    private lateinit var amortizacoes: EditText
    private lateinit var juros: EditText

    private lateinit var decimal: DecimalFormat

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_i_r_p_c, container, false)

        // 初始化DecimalFormat
        val decimalSymbols = DecimalFormatSymbols(Locale("pt", "Brazil"))
        decimalSymbols.decimalSeparator = ','
        decimalSymbols.groupingSeparator = '.'
        decimal = DecimalFormat("###,###,###,###.####", decimalSymbols)

        // 设置EditText
        proveitos = view.findViewById(R.id.proveitos)
        proveitos.setRawInputType(Configuration.KEYBOARD_12KEY)
        proveitos.addTextChangedListener(DecimalTextWatcher(proveitos))

        custos = view.findViewById(R.id.custos)
        custos.setRawInputType(Configuration.KEYBOARD_12KEY)
        custos.addTextChangedListener(DecimalTextWatcher(custos))

        amortizacoes = view.findViewById(R.id.amortizacoes)
        amortizacoes.setRawInputType(Configuration.KEYBOARD_12KEY)
        amortizacoes.addTextChangedListener(DecimalTextWatcher(amortizacoes))

        juros = view.findViewById(R.id.juros)
        juros.setRawInputType(Configuration.KEYBOARD_12KEY)
        juros.addTextChangedListener(DecimalTextWatcher(juros))

        val imposto = view.findViewById<TextView>(R.id.imposto)
        imposto.setTextIsSelectable(true)

        val bt4 = view.findViewById<Button>(R.id.res_final)

        bt4.setOnClickListener {
            iRPC(imposto)
        }

        return view
    }

    private fun iRPC(imposto: TextView) {
        when {
            proveitos.text.isEmpty() && custos.text.isEmpty() && amortizacoes.text.isEmpty() && juros.text.isEmpty() -> {
                Toast.makeText(requireContext(), "Preencha os valores ", Toast.LENGTH_SHORT).show()
            }
            proveitos.text.isEmpty() -> {
                Toast.makeText(requireContext(), "Preencha proveitos", Toast.LENGTH_SHORT).show()
            }
            custos.text.isEmpty() -> {
                Toast.makeText(requireContext(), "Preencha custos", Toast.LENGTH_SHORT).show()
            }
            amortizacoes.text.isEmpty() -> {
                Toast.makeText(requireContext(), "Preencha amortizacoes", Toast.LENGTH_SHORT).show()
            }
            juros.text.isEmpty() -> {
                Toast.makeText(requireContext(), "Preencha juros", Toast.LENGTH_SHORT).show()
            }
            else -> {
                try {
                    // 替换逗号为点,转换为数字
                    val prov = proveitos.text.toString().replace(',', '.').toFloat()
                    val cust = custos.text.toString().replace(',', '.').toFloat()
                    val amort = amortizacoes.text.toString().replace(',', '.').toFloat()
                    val jur = juros.text.toString().replace(',', '.').toFloat()

                    val result = (prov - cust - amort - jur) * 0.32
                    val formatResult = decimal.format(result)

                    imposto.setText(formatResult)
                } catch (e: NumberFormatException) {
                    Toast.makeText(requireContext(), "Valores inválidos", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    // 自定义TextWatcher处理实时格式化
    private class DecimalTextWatcher(private val editText: EditText) : TextWatcher {
        private var isFormatting = false
        private val decimalFormat: DecimalFormat

        init {
            val symbols = DecimalFormatSymbols(Locale("pt", "Brazil"))
            symbols.decimalSeparator = ','
            symbols.groupingSeparator = '.'
            decimalFormat = DecimalFormat("###,###,###,###.####", symbols)
            decimalFormat.isParseBigDecimal = true
        }

        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(s: Editable?) {
            if (isFormatting || s.isNullOrEmpty()) return
            isFormatting = true

            // 清理输入:只保留数字和单个逗号
            val cleanText = s.toString().replace("[^\\d,]".toRegex(), "")
            val singleCommaText = cleanText.replace(",+".toRegex(), ",")

            // 限制小数位最多4位
            val parts = singleCommaText.split(",")
            val processedText = if (parts.size > 1) {
                val decimalPart = parts[1].take(4)
                "${parts[0]},$decimalPart"
            } else {
                singleCommaText
            }

            // 格式化千位分隔符
            val formattedText = try {
                val number = if (processedText.contains(',')) {
                    processedText.replace(',', '.').toBigDecimal()
                } else {
                    processedText.toBigDecimal()
                }
                decimalFormat.format(number)
            } catch (e: NumberFormatException) {
                processedText
            }

            // 保持光标位置正确
            val cursorPosition = editText.selectionStart
            val originalLength = s.length
            val newLength = formattedText.length
            val cursorOffset = if (cursorPosition < originalLength && originalLength > 0 && newLength > 0) {
                val diff = newLength - originalLength
                cursorPosition + diff
            } else {
                newLength
            }

            editText.setText(formattedText)
            editText.setSelection(cursorOffset.coerceAtMost(formattedText.length))

            isFormatting = false
        }
    }
}

关键说明

  • DecimalFormat配置:采用巴西葡萄牙语区域符号,逗号作为小数点、点作为千位分隔符,符合当地数字显示习惯。
  • 输入转换兼容:将EditText中的逗号替换为点后再转换为数字,避免Kotlin数字转换报错。
  • 光标位置优化:格式化时记录并调整光标位置,防止用户输入时光标跳至文本末尾,提升交互流畅度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 08:11:28