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))
二、修正计算结果的显示逻辑
- 转换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()
- 使用格式化后的结果更新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
相关产品推荐
相关产品推荐

