如何在实时编辑后格式化Android EditText中的数字

7

我有一个EditText,用户应该输入包含小数的数字,我希望自动添加千位分隔符到输入的数字。我尝试了几种方法,但有些不允许浮点数,所以我想到了这段代码,它只能很好地工作。但实时编辑的字符串没有可能的千位分隔符,并且错误似乎源于s.replace();

    am2 = new TextWatcher(){
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }
    public void onTextChanged(CharSequence s, int start, int before, int count) {}
    public void afterTextChanged(Editable s) {
        if (s.toString().equals("")) {
            amount.setText("");
            value = 0;
        }else{
            StringBuffer strBuff = new StringBuffer();
            char c;
            for (int i = 0; i < amount2.getText().toString().length() ; i++) {
                c = amount2.getText().toString().charAt(i);
                if (Character.isDigit(c)) {
                    strBuff.append(c);
                }
            }
            value = Double.parseDouble(strBuff.toString());
            reverse();
            NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
            ((DecimalFormat)nf2).applyPattern("###,###.#######");
            s.replace(0, s.length(), nf2.format(value));
        }
    }
};

您可以使用此链接 https://github.com/pouriaHemmati/Thousand-Separators-EditText - Pouria Hemi
7个回答

42

这个类解决了问题,允许输入小数,并添加千位分隔符。

    public class NumberTextWatcher implements TextWatcher {

    private DecimalFormat df;
    private DecimalFormat dfnd;
    private boolean hasFractionalPart;

    private EditText et;

    public NumberTextWatcher(EditText et)
    {
        df = new DecimalFormat("#,###.##");
        df.setDecimalSeparatorAlwaysShown(true);
        dfnd = new DecimalFormat("#,###");
        this.et = et;
        hasFractionalPart = false;
    }

    @SuppressWarnings("unused")
    private static final String TAG = "NumberTextWatcher";

    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);
    }

    public void beforeTextChanged(CharSequence s, int start, int count, int after)
    {
    }

    public void onTextChanged(CharSequence s, int start, int before, int count)
    {
        if (s.toString().contains(String.valueOf(df.getDecimalFormatSymbols().getDecimalSeparator())))
        {
            hasFractionalPart = true;
        } else {
            hasFractionalPart = false;
        }
    }

}

来源: http://blog.roshka.com/2012/08/android-edittext-with-number-format.html


4
如果我希望用户能够输入像这样的数字:“1,234.05”,怎么办? df = new DecimalFormat("#,###.##"); 这个格式不允许逗号后面有零,但如果我改成 df = new DecimalFormat("#,###.0#"); 它会在逗号后自动添加零。那么,你可以添加另一个状态,比如“isFractionalPartThere”,并用以下方式解析它: dfdot = new DecimalFormat("#,###."); 但是,如果您需要允许更高的精度,例如“100.000005”,该怎么办? - Oleksandr Yefremov
2
如何使数字像 1.000 或 1.000.000 或 1.000.000.000? - Amay Diam
有没有一种通用的格式,可以添加千位分隔符但保留小数点后的数字不变呢? - Price
在第15个字母后面添加了000输入。发生了什么? - Shree Krishna
1
这段代码可以工作,但如果人们想要理解它而不是像猴子一样复制粘贴,那么它真的很糟糕。像v、n、cp这样的变量名并不是一个好程序员的标志。 - Billda

7

很遗憾,答案中的代码无法正常工作。

它有两个问题:

  1. 如果手机语言配置使用“,”作为小数分隔符,则无法正常工作。
  2. 如果数字在小数部分有尾随零,则无法正常工作。例如1.01。

我疯狂地尝试修复它。 最终,我得到了以下代码,在我的手机上运行良好:

NumberTextWatcher.java

import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.util.Log;
import android.widget.EditText;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;


public class NumberTextWatcher
        implements TextWatcher {

    private static final String TAG = "NumberTextWatcher";

    private final int numDecimals;
    private String groupingSep;
    private String decimalSep;
    private boolean nonUsFormat;
    private DecimalFormat df;
    private DecimalFormat dfnd;
    private boolean hasFractionalPart;

    private EditText et;
    private String value;


    private String replicate(char ch, int n) {
        return new String(new char[n]).replace("\0", "" + ch);
    }

    public NumberTextWatcher(EditText et, Locale locale, int numDecimals) {

        et.setKeyListener(DigitsKeyListener.getInstance("0123456789.,"));
        this.numDecimals = numDecimals;
        DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);

        char gs = symbols.getGroupingSeparator();
        char ds = symbols.getDecimalSeparator();
        groupingSep = String.valueOf(gs);
        decimalSep = String.valueOf(ds);

        String patternInt = "#,###";
        dfnd = new DecimalFormat(patternInt, symbols);

        String patternDec = patternInt + "." + replicate('#', numDecimals);
        df = new DecimalFormat(patternDec, symbols);
        df.setDecimalSeparatorAlwaysShown(true);
        df.setRoundingMode(RoundingMode.DOWN);

        this.et = et;
        hasFractionalPart = false;

        nonUsFormat = !decimalSep.equals(".");
        value = null;

    }


    @Override
    public void afterTextChanged(Editable s) {
        Log.d(TAG, "afterTextChanged");
        et.removeTextChangedListener(this);

        try {
            int inilen, endlen;
            inilen = et.getText().length();

            String v = value.replace(groupingSep, "");

            Number n = df.parse(v);

            int cp = et.getSelectionStart();
            if (hasFractionalPart) {
                int decPos = v.indexOf(decimalSep) + 1;
                int decLen = v.length() - decPos;
                if (decLen > numDecimals) {
                    v = v.substring(0, decPos + numDecimals);
                }
                int trz = countTrailingZeros(v);

                StringBuilder fmt = new StringBuilder(df.format(n));
                while (trz-- > 0) {
                    fmt.append("0");
                }
                et.setText(fmt.toString());
            } 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 | ParseException nfe) {
            // do nothing?
        }


        et.addTextChangedListener(this);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        Log.d(TAG, "beforeTextChanged");
        value = et.getText().toString();
    }

    private int countTrailingZeros(String str) {
        int count = 0;

        for (int i = str.length() - 1; i >= 0; i--) {
            char ch = str.charAt(i);
            if ('0' == ch) {
                count++;
            } else {
                break;
            }
        }
        return count;
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        Log.d(TAG, "onTextChanged");

        String newValue = s.toString();
        String change = newValue.substring(start, start + count);
        String prefix = value.substring(0, start);
        String suffix = value.substring(start + before);

        if (".".equals(change) && nonUsFormat) {
            change = decimalSep;
        }

        value = prefix + change + suffix;
        hasFractionalPart = value.contains(decimalSep);

        Log.d(TAG, "VALUE: " + value);


    }

}

然后只需简单地使用它执行以下操作:

    Locale locale = new Locale("es", "AR"); // For example Argentina
    int numDecs = 2; // Let's use 2 decimals
    TextWatcher tw = new NumberTextWatcher(myEditText, locale, numDecs);
    myEditText.addTextChangedListener(tw);

2
你需要使用 DecimalFormat 类和 DecimalFormatSymbols 类,查看以下方法的输出:
public static String formatAmount(int num) 
{
    DecimalFormat decimalFormat = new DecimalFormat();
    DecimalFormatSymbols decimalFormateSymbol = new DecimalFormatSymbols();
    decimalFormateSymbol.setGroupingSeparator(',');
    decimalFormat.setDecimalFormatSymbols(decimalFormateSymbol);
    return decimalFormat.format(num);
}

它不起作用了。 错误仍然存在于将EditText更改为具有千位分隔符的新字符串的部分。 - Asiimwe
@PaulAsiimweTumwesigye 我已经进行了全面测试,System.out.println ( formatAmount ( 1234 ) ); 将输出 1,234。 - Lucifer
2
谢谢,是的,我已经尝试使用System.out.println并输出格式化的double值,但我的问题是,在我的Android editText中数字没有改变,当尝试更改时只会出现错误。 - Asiimwe
@PaulAsiimweTumwesigye,StackOverflow 错误,没错,我创建了一个演示应用程序,并得到了同样的错误。等一下,我会带着解决方案回来。 - Lucifer
@PaulAsiimweTumwesigye 我试图使用实时编辑来完成它,但当我尝试将格式化的文本设置到EditText时,afterChanged()方法再次被调用,这样就会无限循环下去,因此会出现 StackOverflow 错误。所以,你不能在实时编辑中完成它。你可以在输入后使用我的方法来格式化数字。 - Lucifer
2
你需要先移除TextWatcher再进行编辑,之后再重新添加回去。 - Thommy

1

您可以像这样使用Kotlin扩展函数...

fun EditText.onCommaChange(input: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        if (!edit) {
            edit = true
            if (s.toString() != "₹") {
                try {
                    val flNumber = getCommaLessNumber(s.toString()).toInt()
                    val fNumber = getFormattedAmount(flNumber)
                    setText(fNumber)
                    setSelection(text.length)
                    input(flNumber.toString())
                } catch (e: NumberFormatException) {
                    Timber.e(e)
                }
            } else {
                setText("")
                input("")
            }
            edit = false
        }
    }

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

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

fun getCommaLessNumber(commaNumber: String): String {
var number = commaNumber.replace("₹", "")
number = number.replace(",".toRegex(), "")
return number}

fun getFormattedAmount(amount: Int): String {
return "₹${String.format("%,d", amount)}"}

fun EditText.text() = this.text.toString()

当你想要一个带有货币符号的数字格式时,这非常有用。 - Bhojaviya Sagar
只需将上面的 fun EditText.text() = this.text.toString() 更改为 fun EditText.text(input: String) = getCommaLessNumber(this.text.toString()),这样,当您想要在任何地方获取文本时,它将返回未格式化的整数。 - Bhojaviya Sagar

0

我在 Kotlin 中使用了这种方式来创建一个 Dialog

val et = dialog.findViewById(R.id.etNumber) as EditText
et.addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(s: Editable) {

                    et.removeTextChangedListener(this)
                    forChanged(et)
                    et.addTextChangedListener(this)
                }

                override fun beforeTextChanged(
                    s: CharSequence,
                    start: Int,
                    count: Int,
                    after: Int
                ) {

                }

                override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {

                }
            })

然后编写像这样的方法:

private fun forChanged(alpha: EditText) {
        val string = alpha.text.toString()
        val dec = DecimalFormat("#,###")
        if (!TextUtils.isEmpty(string)) {
            val textWC = string.replace(",".toRegex(), "")
            val number = textWC.toDouble()
            alpha.setText(dec.format(number))
            alpha.setSelection(dec.format(number).length)
        }
    }

0
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import java.text.DecimalFormat;
public class MyNumberWatcher_3Digit implements TextWatcher {
    private EditText editText;
    private int digit;


    public MyNumberWatcher_3Digit(EditText editText) {
        this.editText = editText;

    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void afterTextChanged(Editable editable) {
        editText.removeTextChangedListener( this );

        String s = editText.getText().toString();
        s = s.replace( ",", "" ).replace( "٬", "" );
        s = replaceNonstandardDigits( s );
        if (s.length() > 0) {
            DecimalFormat sdd = new DecimalFormat( "#,###" );
            Double doubleNumber = Double.parseDouble( s );

            String format = sdd.format( doubleNumber );
            editText.setText( format );
            editText.setSelection( format.length() );

        }
        editText.addTextChangedListener( this );
    }


    static String replaceNonstandardDigits(String input) {
        if (input == null || input.isEmpty()) {
            return input;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            char ch = input.charAt( i );
            if (isNonstandardDigit( ch )) {
                int numericValue = Character.getNumericValue( ch );
                if (numericValue >= 0) {
                    builder.append( numericValue );
                }
            } else {
                builder.append( ch );
            }
        }
        return builder.toString();
    }

    private static boolean isNonstandardDigit(char ch) {
        return Character.isDigit( ch ) && !(ch >= '0' && ch <= '9');
    }


}

// 创建活动

  input_text_rate.addTextChangedListener(new MyNumberWatcher_3Digit(input_text_rate));

0

我尝试了一些解决方案,但是以0结尾的数字让我遇到了问题,有时候用户只想输入0.01或0.0001

我不知道是否已经有其他人发表了相同的答案,但如果这对你有帮助的话,

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;

import java.text.DecimalFormat;

public class NumberTextWatcher implements TextWatcher {
    private DecimalFormat dfnd;
    private boolean hasFractionalPart;

    private EditText inputView;

    public static void bindView(EditText inputView) {
        NumberTextWatcher temp = new NumberTextWatcher(inputView);
        inputView.addTextChangedListener(temp);
    }

    public NumberTextWatcher(EditText inputView) {
        dfnd = new DecimalFormat("#,###.######");
        this.inputView = inputView;
        hasFractionalPart = false;
    }

    @SuppressWarnings("unused")
    private static final String TAG = "NumberTextWatcher";

    public void afterTextChanged(Editable s) {
        Log.d(TAG, "afterTextChanged() called with: s = [" + s + "]");
        inputView.removeTextChangedListener(this);

        try {
            String text = inputView.getText().toString().replace(String.valueOf(dfnd.getDecimalFormatSymbols().getGroupingSeparator()), "");
            if(text.charAt(text.length() - 1) == '.')
            {
                if(getCount(text,'.') >1)
                {
                    text = text.substring(0,text.length()-1);
                }
            }
            String afterDecimalPoint = "";
            String beforeDecimalPoint = text;
            if (hasFractionalPart || (text.charAt(text.length() - 1) == '0')) {
                String[] data = text.split("\\.");
                beforeDecimalPoint = data[0];
                if (data.length != 2) {
                    afterDecimalPoint = ".";
                } else {
                    afterDecimalPoint = "." + data[1];
                    if (data[1].length() >= dfnd.getMaximumFractionDigits()) {
                        afterDecimalPoint = "." + data[1].substring(0, dfnd.getMaximumFractionDigits());
                    }
                }
            }
            beforeDecimalPoint = dfnd.format(Double.parseDouble(beforeDecimalPoint));
            String finalText = beforeDecimalPoint;
            if (hasFractionalPart) {
                finalText = beforeDecimalPoint + afterDecimalPoint;
            }
            inputView.setText(finalText);
            inputView.setSelection(finalText.length());

        } catch (Exception nfe) {
            // do nothing?
            nfe.printStackTrace();
        }

        inputView.addTextChangedListener(this);
    }

    private int getCount(String someString, char someChar) {
        int count = 0;

        for (int i = 0; i < someString.length(); i++) {
            if (someString.charAt(i) == someChar) {
                count++;
            }
        }
        return count;
    }

    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (s.toString().contains(String.valueOf(dfnd.getDecimalFormatSymbols().getDecimalSeparator()))) {
            hasFractionalPart = true;
        } else {
            hasFractionalPart = false;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接