7

I have an EditText in which the user should input a number including decimals and i want a thousand separator automatically added onto the input number I tried a couple of other methods but some do not allow floating point numbers so i came up with this code which works well only that the string input is not being edited in realtime to one with possible thousand separators and the errors seem to stem from the 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));
        }
    }
};
Lucifer
  • 29,392
  • 25
  • 90
  • 143
Asiimwe
  • 2,199
  • 5
  • 24
  • 36

7 Answers7

42

This Class solves the problem, allows decimal input and adds the thousand separators.

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

}

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

Asiimwe
  • 2,199
  • 5
  • 24
  • 36
  • 4
    How about if I want user to be able to enter number like this: "1,234.05" ? `df = new DecimalFormat("#,###.##");` This format does not allow zeros after comma, but if I change it to `df = new DecimalFormat("#,###.0#");` it will automatically add zero after comma. Well, you can add another state like "isFractionalPartThere" and parse it with `dfdot = new DecimalFormat("#,###.");` but how would you go about if you need to allow more precision "100.000005" ? – Oleksandr Yefremov Sep 12 '14 at 06:35
  • 2
    how make number like 1.000 or 1.000.000 or 1.000.000.000 ? – Amay Diam Nov 04 '14 at 07:20
  • Is there no generic format that adds thousand separators but leaves the decimals untouched? – Price Jul 18 '15 at 09:00
  • It is adding 000 input after 15th letter. what happened ?? – Shree Krishna Dec 11 '15 at 09:47
  • 1
    This code works, but if people wants to understand it and not just copy-paste it like monkeys, its really awful. Variable names like v, n, cp are not sign of a good programmer – Billda Dec 29 '17 at 09:59
7

Unfortunately the code did not work as it is in the answer.

It has two problems:

  1. It does not work if the phone locale configuration uses "," as a decimal separator.
  2. It does not work if the number has trailing zeros in the decimal part. Example 1.01.

I went crazy to fix it. Finally I came to this code that worked well on my cell phone:

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


    }

}

and then to use it simply to do:

    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);
estebanuri
  • 848
  • 1
  • 10
  • 11
2

You need to use DecimalFormat class with DecimalFormatSymbols class, check the out following method,

public static String formatAmount(int num) 
{
    DecimalFormat decimalFormat = new DecimalFormat();
    DecimalFormatSymbols decimalFormateSymbol = new DecimalFormatSymbols();
    decimalFormateSymbol.setGroupingSeparator(',');
    decimalFormat.setDecimalFormatSymbols(decimalFormateSymbol);
    return decimalFormat.format(num);
}
Lucifer
  • 29,392
  • 25
  • 90
  • 143
  • it is not working.. error still on the part where the EditText is changed to the new String with the thousand separators – Asiimwe Sep 22 '12 at 09:07
  • @PaulAsiimweTumwesigye i have fully tested , `System.out.println ( formatAmount ( 1234 ) );` will give output 1,234 – Lucifer Sep 22 '12 at 09:09
  • 2
    thank you yes i have tried it using System.out.println and it outputs the formatted double, my problem is that in my android editText the numbers are not changing it just gets an error when trying to change it. – Asiimwe Sep 22 '12 at 09:20
  • @PaulAsiimweTumwesigye, **StackOverflow** error, right, yes, i created a demo app, and got same, wait a min, i will be back with solution. – Lucifer Sep 22 '12 at 09:26
  • @PaulAsiimweTumwesigye i tired to do it in real time editing, but as when i tried to set the formatted text to EditText, again the afterChanged() method gets called, and this goes on & on in infinite loop, so it is giving me **StackOverflow** error. hence you can't do it in real time editing. you may use my method to format the number once you entered. – Lucifer Sep 22 '12 at 10:12
  • 2
    You have to remove the TextWatcher do your edits and than add it again. – Thommy Jan 02 '13 at 09:03
1

you can use kotlin extensions function like this...

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()
Makwana Mehul
  • 365
  • 3
  • 3
  • This very useful when you want a currency symbol with a number format – Bhojaviya Sagar Jan 08 '21 at 13:47
  • Just change above fun EditText.text() = this.text.toString() to fun EditText.text(input: String) = getCommaLessNumber(this.text.toString()) so, when you want to get text any place it will return integer number without formatted – Bhojaviya Sagar Jan 09 '21 at 06:30
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');
    }


}

// oncreate activity

  input_text_rate.addTextChangedListener(new MyNumberWatcher_3Digit(input_text_rate));
Ak.Ha
  • 21
  • 3
0

I used this way in Kotlin for a 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) {

                }
            })

then write a method like this:

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)
        }
    }
Mori
  • 2,653
  • 18
  • 24
0

I have tried solutions but with ending 0 i was having problem, sometimes user just wanted to enter 0.01 or 0.0001,

I don't know if any other have posted same answer or not but if this helps,

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;
        }
    }
}
Ashvin solanki
  • 4,802
  • 3
  • 25
  • 65