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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 17:18:20