Android TextWatcher数字格式化:整数.分组小数,分隔实现问题
问题描述
给EditText绑定addTextChangedListener时,通过自定义TextWatcher实现数字输入格式化,当前格式化输出示例为123,456,77,预期输出格式为123.456,77:整数部分千分位分隔符使用.,小数部分分隔符使用,。
直接在afterTextChanged方法中对EditText内容做逗号替换为点的操作,会导致数字格式化逻辑完全失效;仅修改DecimalFormat的格式模式为"###,##0,00"也无法实现预期效果。
原有实现代码如下:
import android.text.Editable; import android.text.TextWatcher; import android.widget.EditText; import java.text.DecimalFormat; import java.text.ParseException; import java.util.Locale; public class NumberTextWatcher implements TextWatcher { private DecimalFormat df; private DecimalFormat dfnd; private boolean hasFractionalPart; private EditText et; public NumberTextWatcher(EditText et) { df = new DecimalFormat("###,##0,00"); df.setDecimalSeparatorAlwaysShown(true); dfnd = new DecimalFormat("###,##0,00"); this.et = et; hasFractionalPart = false; } @SuppressWarnings("unused") private static final String TAG = "NumberTextWatcher"; @Override public void afterTextChanged(Editable s) { et.removeTextChangedListener(this); try { int inilen, endlen; inilen = et.getText().length(); String v = s.toString().replace(String.valueOf(df.getDecimalFormatSymbols().getGroupingSeparator()), ""); Number n = df.parse(v); int cp = et.getSelectionStart(); if (hasFractionalPart) { et.setText(df.format(n)); } else { et.setText(dfnd.format(n)); } endlen = et.getText().length(); int sel = (cp + (endlen - inilen)); if (sel > 0 && sel <= et.getText().length()) { et.setSelection(sel); } else { // place cursor at the end? et.setSelection(et.getText().length() - 1); } } catch (NumberFormatException nfe) { // do nothing? } catch (ParseException e) { // do nothing? } et.addTextChangedListener(this); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { int index = s.toString().indexOf(String.valueOf(df.getDecimalFormatSymbols().getDecimalSeparator())); int trailingZeroCount = 0; if (index > -1) { for (index++; index < s.length(); index++) { if (s.charAt(index) == '0') trailingZeroCount++; else { trailingZeroCount = 0; } } hasFractionalPart = true; } else { hasFractionalPart = false; } { if (s.toString().contains(String.valueOf(df.getDecimalFormatSymbols().getDecimalSeparator()))) { hasFractionalPart = true; } else { hasFractionalPart = false; } } } }
问题原因
DecimalFormat的模式字符串中,,固定代表分组分隔符占位、.固定代表小数分隔符占位,和实际输出的字符没有关系,实际输出的分隔符由DecimalFormatSymbols配置决定,默认跟随当前系统Locale的格式规则。直接修改模式字符串里的字符、或者格式化后硬替换字符串,都会打乱分隔符的识别逻辑,导致解析、格式化流程失效。
修复方案
通过自定义DecimalFormatSymbols指定需要的分隔符,同时修正模式字符串写法、清理冗余逻辑,修改后的完整实现如下:
import android.text.Editable; import android.text.TextWatcher; import android.widget.EditText; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.ParseException; public class NumberTextWatcher implements TextWatcher { private DecimalFormat df; private DecimalFormat dfnd; private boolean hasFractionalPart; private EditText et; private char groupSeparator; private char decimalSeparator; public NumberTextWatcher(EditText et) { // 自定义格式符号:千分位用.,小数位用, DecimalFormatSymbols symbols = new DecimalFormatSymbols(); symbols.setGroupingSeparator('.'); symbols.setDecimalSeparator(','); groupSeparator = symbols.getGroupingSeparator(); decimalSeparator = symbols.getDecimalSeparator(); // 模式字符串用标准写法:,代表分组位,.代表小数位,实际输出字符由symbols控制 df = new DecimalFormat("###,##0.00", symbols); df.setDecimalSeparatorAlwaysShown(true); dfnd = new DecimalFormat("###,##0.00", symbols); this.et = et; hasFractionalPart = false; } @Override public void afterTextChanged(Editable s) { et.removeTextChangedListener(this); try { int inilen = et.getText().length(); // 清理输入:移除所有自动添加的千分位分隔符,保留小数分隔符 String v = s.toString().replace(String.valueOf(groupSeparator), ""); Number n = df.parse(v); int cp = et.getSelectionStart(); if (hasFractionalPart) { et.setText(df.format(n)); } else { et.setText(dfnd.format(n)); } int endlen = et.getText().length(); int sel = cp + (endlen - inilen); if (sel > 0 && sel <= et.getText().length()) { et.setSelection(sel); } else { et.setSelection(et.getText().length()); } } catch (ParseException e) { e.printStackTrace(); } finally { et.addTextChangedListener(this); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // 仅保留一次小数位判断逻辑,删除未使用的冗余变量 hasFractionalPart = s.toString().contains(String.valueOf(decimalSeparator)); } }
关键修改点
- 模式字符串保持标准写法
###,##0.00,不直接在模式里写自定义分隔符 - 新建
DecimalFormatSymbols实例,手动指定分组分隔符为.、小数分隔符为,,传入DecimalFormat构造方法生效 - 提前缓存两个分隔符字符,所有分隔符判断、清理逻辑都使用缓存值,不硬编码
- 删除原
onTextChanged中重复的小数位判断、未使用的trailingZeroCount变量,简化逻辑 - 把addTextChangedListener放到finally块中,避免异常发生时监听器无法重新绑定
- 修正光标定位逻辑,无小数位时光标默认放到文本末尾,不会错位到倒数第二位
内容的提问来源于stack exchange,提问作者ivan14
相关产品推荐
相关产品推荐

