74

Im creating a convertor application. I want to set the EditText so that when the user is inputting the number to be converted, a thousand separator (,) should be added automatically in realtime to the number once it increments by 3 figures: thousand, million, billion etc.
And when erased to below 4 figures the number goes back to normal.
Any help?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Asiimwe
  • 2,199
  • 5
  • 24
  • 36
  • Thank you Taras but that issue was for if you wanted to replace the DECIMAL separator with a comma. Im referring to when you type "1000" and it appears as "1,000" – Asiimwe Sep 09 '12 at 11:14
  • Decimal nuber separator is described here: http://stackoverflow.com/q/3821539/1645319 . – Taras Shevchuk Sep 09 '12 at 11:15
  • @PaulAsiimwe Did you find any solution to it? i.e. Changing "1000" to "1,000" as the user is typing. – Saim Mehmood Jul 01 '15 at 12:09
  • @saim Mehmood check my profile for a similar question with the word "realtime editing" – Asiimwe Aug 19 '15 at 13:49
  • http://stackoverflow.com/questions/25823579/edittext-and-textview-formatted-with-thousands-separators-in-android – Amir K. Zarini Aug 31 '15 at 11:52
  • Found the Best answer [here](http://stackoverflow.com/questions/12542211/how-to-format-number-in-android-edittext-after-realtime-editing), solves the problem and more – Asiimwe Dec 10 '15 at 09:46
  • http://stackoverflow.com/questions/4172242/live-editing-of-users-input/37187857#37187857 – HenryChuang May 12 '16 at 13:18
  • Check it below link: the simple way is, https://stackoverflow.com/a/59981358/11675817 – M Karimi Jan 30 '20 at 08:09

16 Answers16

61

Even-though It's late. Intended for future visitors.

Fetures of the following codes

  1. Puts thousand separator in EditText as it's text changes.

  2. adds 0. Automatically when pressed period (.) At First.

  3. Ignores 0 input at Beginning.

Just copy the following Class named

NumberTextWatcherForThousand which implements TextWatcher

import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import java.util.StringTokenizer;

/**
 * Created by skb on 12/14/2015.
 */
public class NumberTextWatcherForThousand implements TextWatcher {

    EditText editText;


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


    }

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

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        try
        {
            editText.removeTextChangedListener(this);
            String value = editText.getText().toString();


            if (value != null && !value.equals(""))
            {

                if(value.startsWith(".")){
                    editText.setText("0.");
                }
                if(value.startsWith("0") && !value.startsWith("0.")){
                    editText.setText("");

                }


                String str = editText.getText().toString().replaceAll(",", "");
                if (!value.equals(""))
                editText.setText(getDecimalFormattedString(str));
                editText.setSelection(editText.getText().toString().length());
            }
            editText.addTextChangedListener(this);
            return;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            editText.addTextChangedListener(this);
        }

    }

    public static String getDecimalFormattedString(String value)
    {
        StringTokenizer lst = new StringTokenizer(value, ".");
        String str1 = value;
        String str2 = "";
        if (lst.countTokens() > 1)
        {
            str1 = lst.nextToken();
            str2 = lst.nextToken();
        }
        String str3 = "";
        int i = 0;
        int j = -1 + str1.length();
        if (str1.charAt( -1 + str1.length()) == '.')
        {
            j--;
            str3 = ".";
        }
        for (int k = j;; k--)
        {
            if (k < 0)
            {
                if (str2.length() > 0)
                    str3 = str3 + "." + str2;
                return str3;
            }
            if (i == 3)
            {
                str3 = "," + str3;
                i = 0;
            }
            str3 = str1.charAt(k) + str3;
            i++;
        }

    }

    public static String trimCommaOfString(String string) {
//        String returnString;
        if(string.contains(",")){
            return string.replace(",","");}
        else {
            return string;
        }

    }
}

Use This Class on your EditText as follows

editText.addTextChangedListener(new NumberTextWatcherForThousand(editText));

To get the input as plain Double Text

Use the trimCommaOfString method of the same class like this

NumberTextWatcherForThousand.trimCommaOfString(editText.getText().toString())

Git

Shree Krishna
  • 8,474
  • 6
  • 40
  • 68
  • 1
    Have problem with key delete. – Holi Boom Jun 13 '16 at 07:14
  • 16
    This is _horrible_ for anyone who doesn't use the English form of number formats, so anyone in Europe, French Canada, South America, Asia, etc Rather than using a hard-coded comma, you should look at DecimalFormatSymbols() and take getGroupingSeparator() instead. Remember that while the US & UK use comma for thousands and full stop for decimals, a lot of the European languages use dot for thousands & comma for decimals, others use spaces for thousands. Please think multi-locale rather than just your own. – Phil A Dec 05 '16 at 13:50
  • 1
    Great answer !!! TNX. it draws the path great and anyone with any locale can easily use it with just a little modifying... – MHSaffari Dec 02 '18 at 13:57
  • How to handle this for a million value. e.g., 1,000,000 – Axay Prajapati Jun 18 '19 at 09:34
56

You can use String.format() in a TextWatcher. The comma in the format specifier does the trick.

This does not work for floating point input. And be careful not to set an infinite loop with the TextWatcher.

public void afterTextChanged(Editable view) {
    String s = null;
    try {
        // The comma in the format specifier does the trick
        s = String.format("%,d", Long.parseLong(view.toString()));
    } catch (NumberFormatException e) {
    }
    // Set s back to the view after temporarily removing the text change listener
}
Dheeraj Vepakomma
  • 26,870
  • 17
  • 81
  • 104
39
public static String doubleToStringNoDecimal(double d) {
    DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.US);
    formatter.applyPattern("#,###");
    return formatter.format(d);
}
doej1367
  • 311
  • 2
  • 12
user2261183
  • 419
  • 4
  • 4
  • 1
    it will put separator only for first "thousand" (1,234). and if you'll have number like million - your separator will separate only first thousand (1,23456789) – kazimad Jan 20 '21 at 10:37
11

This sample app deconstructs formatting numbers clearly.

To summarize the link above, use a TextWatcher and in the afterTextChanged() method format the EditText view with the following logic:

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

    try {
        String originalString = s.toString();

        Long longval;
        if (originalString.contains(",")) {
            originalString = originalString.replaceAll(",", "");
        }
        longval = Long.parseLong(originalString);

        DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.US);
        formatter.applyPattern("#,###,###,###");
        String formattedString = formatter.format(longval);

        //setting text after format to EditText
        editText.setText(formattedString);
        editText.setSelection(editText.getText().length());
    } catch (NumberFormatException nfe) {
        nfe.printStackTrace();
    }

    editText.addTextChangedListener(this);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
AdamHurwitz
  • 9,758
  • 10
  • 72
  • 134
  • 1
    This is nice except if your cursor is editing somewhere in the middle of the number rather than the end, the cursor gets moved to the end rather than staying where it was. – vlazzle Sep 01 '17 at 03:26
  • just want to confirm this method works but affects the performance a lot – Danish Ansari May 15 '19 at 04:53
7

I know i am very late to the party but it may be very useful for future users. My answer is an extension of Shree Krishna's answer.

Improvements:

  1. Thousands separators and Decimal markers are locale aware i.e. they are used accordingly to the Locale of the device.
  2. The cursor position doesn't change after deleting or adding elements in the middle also (In his answer cursor was reset to the end).
  3. The overall quality of the code has been improved specially the getDecimalFormattedString method.

Code:

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

    import java.text.DecimalFormat;


    /**
     * Created by srv_twry on 4/12/17.
     * Source: https://stackoverflow.com/a/34265406/137744
     * The custom TextWatcher that automatically adds thousand separators in EditText.
     */

    public class ThousandSeparatorTextWatcher implements TextWatcher {

        private DecimalFormat df;
        private EditText editText;
        private static String thousandSeparator;
        private static String decimalMarker;
        private int cursorPosition;

        public ThousandSeparatorTextWatcher(EditText editText) {
            this.editText = editText;
            df = new DecimalFormat("#,###.##");
            df.setDecimalSeparatorAlwaysShown(true);
            thousandSeparator = Character.toString(df.getDecimalFormatSymbols().getGroupingSeparator());
            decimalMarker = Character.toString(df.getDecimalFormatSymbols().getDecimalSeparator());
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
            cursorPosition = editText.getText().toString().length() - editText.getSelectionStart();
        }

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

        @Override
        public void afterTextChanged(Editable s) {
            try {
                editText.removeTextChangedListener(this);
                String value = editText.getText().toString();

                if (value != null && !value.equals("")) {
                    if (value.startsWith(decimalMarker)) {
                        String text = "0" + decimalMarker;
                        editText.setText(text);
                    }
                    if (value.startsWith("0") && !value.startsWith("0" + decimalMarker)) {
                        int index = 0;
                        while (index < value.length() && value.charAt(index) == '0') {
                            index++;
                        }
                        String newValue = Character.toString(value.charAt(0));
                        if (index != 0) {
                            newValue = value.charAt(0) + value.substring(index);
                        }
                        editText.setText(newValue);
                    }
                    String str = editText.getText().toString().replaceAll(thousandSeparator, "");
                    if (!value.equals("")) {
                        editText.setText(getDecimalFormattedString(str));
                    }
                    editText.setSelection(editText.getText().toString().length());
                }

                //setting the cursor back to where it was
                editText.setSelection(editText.getText().toString().length() - cursorPosition);
                editText.addTextChangedListener(this);
            } catch (Exception ex) {
                ex.printStackTrace();
                editText.addTextChangedListener(this);
            }
        }

        private static String getDecimalFormattedString(String value) {

            String[] splitValue = value.split("\\.");
            String beforeDecimal = value;
            String afterDecimal = null;
            String finalResult = "";

            if (splitValue.length == 2) {
                beforeDecimal = splitValue[0];
                afterDecimal = splitValue[1];
            }

            int count = 0;
            for (int i = beforeDecimal.length() - 1; i >= 0 ; i--) {
                finalResult = beforeDecimal.charAt(i) + finalResult;
                count++;
                if (count == 3 && i > 0) {
                    finalResult = thousandSeparator + finalResult;
                    count = 0;
                }
            }

            if (afterDecimal != null) {
                finalResult = finalResult + decimalMarker + afterDecimal;
            }

            return finalResult;
        }

        /*
        * Returns the string after removing all the thousands separators.
        * */
        public static String getOriginalString(String string) {
            return string.replace(thousandSeparator,"");
        }
    }
dr0pdb
  • 83
  • 1
  • 7
4

This solution has some advantage over other answers. For example, it keeps the user's cursor position even if they edit the beginning or middle of the number. Other solutions always jump the cursor to the end of the number. It handles decimals and whole numbers, as well as locales that use characters other than . for the decimal separator and , for the thousands grouping separator.

class SeparateThousands(val groupingSeparator: String, val decimalSeparator: String) : TextWatcher {

    private var busy = false

    override fun afterTextChanged(s: Editable?) {
        if (s != null && !busy) {
            busy = true

            var place = 0

            val decimalPointIndex = s.indexOf(decimalSeparator)
            var i = if (decimalPointIndex == -1) {
                s.length - 1
            } else {
                decimalPointIndex - 1
            }
            while (i >= 0) {
                val c = s[i]
                if (c == groupingSeparator[0] ) {
                    s.delete(i, i + 1)
                } else {
                    if (place % 3 == 0 && place != 0) {
                        // insert a comma to the left of every 3rd digit (counting from right to
                        // left) unless it's the leftmost digit
                        s.insert(i + 1, groupingSeparator)
                    }
                    place++
                }
                i--
            }

            busy = false
        }
    }

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

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

Then in xml:

  <EditText
    android:id="@+id/myNumberField"
    android:digits=",.0123456789"
    android:inputType="numberDecimal"
    .../>

And finally register the watcher:

findViewById(R.id.myNumberField).addTextChangedListener(
    SeparateThousands(groupingSeparator, decimalSeparator))

To handle . vs , in different locales use groupingSeparator and decimalSeparator, which can come from DecimalFormatSymbols or localized strings.

Kofi
  • 525
  • 6
  • 15
vlazzle
  • 811
  • 9
  • 14
  • I know it's late but thanks for this. It's working well on my project. However, when used with `android:maxLength`, the `groupingSeparator` appears and disappears as you type. Is there any way to use `android:maxLength` for `numbers` only? – Kellin Strook Oct 27 '22 at 04:10
4

I just wanted comma to be placed and this is working for me:

String.format("%,.2f", myValue);
Zohab Ali
  • 8,426
  • 4
  • 55
  • 63
2

you can use this code in many ways in your program, you give it a string and it separate each three from right and place space there.

private String Spacer(String number){
    StringBuilder strB = new StringBuilder();
    strB.append(number);
    int Three = 0;

    for(int i=number.length();i>0;i--){
        Three++;
        if(Three == 3){
            strB.insert(i-1, " ");
            Three = 0;
        }
    }
    return strB.toString();
}// end Spacer()

u can change it a bit and use it ontextchangelistener. good luck

Mohad Hadi
  • 1,826
  • 3
  • 24
  • 39
2

Here is my ThousandNumberEditText class

public class ThousandNumberEditText extends android.support.v7.widget.AppCompatEditText {
    // TODO: 14/09/2017 change it if you want 
    private static final int MAX_LENGTH = 20;
    private static final int MAX_DECIMAL = 3;

    public ThousandNumberEditText(Context context) {
        this(context, null);
    }

    public ThousandNumberEditText(Context context, AttributeSet attrs) {
        this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle);
    }

    public ThousandNumberEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        addTextChangedListener(new ThousandNumberTextWatcher(this));
        setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
        setFilters(new InputFilter[] { new InputFilter.LengthFilter(MAX_LENGTH) });
        setHint("0"); // TODO: 14/09/2017 change it if you want 
    }

    private static class ThousandNumberTextWatcher implements TextWatcher {

        private EditText mEditText;

        ThousandNumberTextWatcher(EditText editText) {
            mEditText = 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) {
            String originalString = editable.toString();
            String cleanString = originalString.replaceAll("[,]", "");
            if (cleanString.isEmpty()) {
                return;
            }
            String formattedString = getFormatString(cleanString);

            mEditText.removeTextChangedListener(this);
            mEditText.setText(formattedString);
            mEditText.setSelection(mEditText.getText().length());
            mEditText.addTextChangedListener(this);
        }

        /**
         * Return the format string
         */
        private String getFormatString(String cleanString) {
            if (cleanString.contains(".")) {
                return formatDecimal(cleanString);
            } else {
                return formatInteger(cleanString);
            }
        }

        private String formatInteger(String str) {
            BigDecimal parsed = new BigDecimal(str);
            DecimalFormat formatter;
            formatter = new DecimalFormat("#,###");
            return formatter.format(parsed);
        }

        private String formatDecimal(String str) {
            if (str.equals(".")) {
                return ".";
            }
            BigDecimal parsed = new BigDecimal(str);
            DecimalFormat formatter;
            formatter =
                    new DecimalFormat("#,###." + getDecimalPattern(str)); //example patter #,###.00
            return formatter.format(parsed);
        }

        /**
         * It will return suitable pattern for format decimal
         * For example: 10.2 -> return 0 | 10.23 -> return 00 | 10.235 -> return 000
         */
        private String getDecimalPattern(String str) {
            int decimalCount = str.length() - 1 - str.indexOf(".");
            StringBuilder decimalPattern = new StringBuilder();
            for (int i = 0; i < decimalCount && i < MAX_DECIMAL; i++) {
                decimalPattern.append("0");
            }
            return decimalPattern.toString();
        }
    }
}

Using

<.ThousandNumberEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />
Linh
  • 57,942
  • 23
  • 262
  • 279
2

You can use this method:

myEditText.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {

                    String input = s.toString();

                    if (!input.isEmpty()) {

                        input = input.replace(",", "");

                        DecimalFormat format = new DecimalFormat("#,###,###");
                        String newPrice = format.format(Double.parseDouble(input));


                        myEditText.removeTextChangedListener(this); //To Prevent from Infinite Loop

                        myEditText.setText(newPrice);
                        myEditText.setSelection(newPrice.length()); //Move Cursor to end of String

                        myEditText.addTextChangedListener(this);
                    }

                }

                @Override
                public void afterTextChanged(final Editable s) {
                }
            });

And to get original text use this:

String input = myEditText.getText().toString();
input = input.replace(",", "");
double-beep
  • 5,031
  • 17
  • 33
  • 41
Alireza K
  • 309
  • 3
  • 5
1

Since i had the same problem i decided to find a solution to it

Find my function below i hope it helps people finding solution

securityDeposit.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start,
                    int before, int count) {
                // TODO Auto-generated method stub

            }

            @Override
            public void beforeTextChanged(CharSequence s, int start,
                    int before, int count) {
                // TODO Auto-generated method stub

            }

            @Override
            public void afterTextChanged(Editable s) {
                // TODO Auto-generated method stub
                if (s.toString().trim().length() > 0) {
                    int rentValue = Integer.parseInt(s.toString()
                            .replaceAll(",", ""));
                    StringBuffer rentVal = new StringBuffer();
                    if (rentValue > 10000000) {
                        s.clear();
                        s.append("10,000,000");
                    } else {

                        if (s.length() == 4) {
                            char x[] = s.toString().toCharArray();

                            char y[] = new char[x.length + 1];
                            for (int z = 0; z < y.length; z++) {

                                if (z == 1) {
                                    y[1] = ',';

                                } else {
                                    if (z == 0)
                                        y[z] = x[z];
                                    else {
                                        y[z] = x[z - 1];
                                    }
                                }

                            }

                            for (int z = 0; z < y.length; z++) {
                                rentVal = rentVal.append(y[z]);
                            }

                            s.clear();
                            s.append(rentVal);

                        }

                    }
                }

            }
        });
user1530779
  • 409
  • 5
  • 8
1

The answers here lack a method to handle actual user input, such as deleting characters or copying and pasting. This is an EditText field. If you want to add formatting in, you need to support editing that formatted value.

This implementation still has a deficiency depending on your use case. I didn't care about decimal values and assumed I would only be handling whole numbers. There's enough of how to handle that on this page and how to handle actual internationalization that I'll leave that as an exercise to the reader. If you need to do that, it shouldn't be too difficult to add "." to the regular expression to keep the decimal; you'll just have to be careful to acknowledge the numeral string still has a non numerical character.

This is designed to be used throughout multiple activities. New it once, give it your edit text and your data model and ignore it. The model binding can be removed if you don't need it.

public class EditNumberFormatter implements TextWatcher {

    private EditText watched;
    private Object model;
    private Field field;
    private IEditNumberFormatterListener listener;

    private ActiveEdit activeEdit;

    /**
     * Binds an EditText to a data model field (Such as a room entity's public variable)
     * Whenever the edit text is changed, the text is formatted to the local numerical format.
     *
     * Handles copy/paste/backspace/select&delete/typing
     *
     * @param model An object with a public field to bind to
     * @param fieldName A field defined on the object
     * @param watched The edit text to watch for changes
     * @param listener Another object that wants to know after changes & formatting are done.
     */
    public EditNumberFormatter(Object model, String fieldName, EditText watched, IEditNumberFormatterListener listener) {

        this.model = model;
        this.watched = watched;
        this.listener = listener;

        try {
            field = model.getClass().getDeclaredField(fieldName);
        } catch(Exception e) { }

        watched.addTextChangedListener(this);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        activeEdit = new ActiveEdit(s.toString(), start, count);
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        activeEdit.recordChangedText(s.toString(),count);
    }

    @Override
    public void afterTextChanged(Editable s) {
        this.watched.removeTextChangedListener(this);

        activeEdit.processEdit(); // Override the user's edit of the formatted string with what the user intended to do to the numeral.

        watched.setText(activeEdit.getCurrentFormattedString());
        watched.setSelection(activeEdit.getCursorPosition());
        updateDataModel(activeEdit.getCurrentRawValue());

        listener.FormatUpdated(watched.getId(), activeEdit.getCurrentRawValue(), activeEdit.getCurrentFormattedString());

        this.watched.addTextChangedListener(this);
    }

    private void updateDataModel(int rawValue) {
        try {
            field.set(model, rawValue);
        } catch (IllegalAccessException e) { }
    }

    /**
     * Tracks the active editing of an EditText formatted for integer input
     */
    private class ActiveEdit {

        private String priorFormattedString;
        private String currentFormattedString;
        private String currentNumericalString;
        private int currentRawValue;

        private boolean removal;
        private boolean addition;

        private int changeStart;
        private int removedCount;
        private int additionCount;

        private int numeralCountBeforeSelection;
        private int numeralCountAdded;
        private int numeralCountRemoved;

        /**
         * Call in beforeEdit to begin recording changes
         *
         * @param beforeEdit string before edit began
         * @param start start position of edit
         * @param removed number of characters removed
         */
        public ActiveEdit(String beforeEdit, int start, int removed) {
            removal = (removed > 0);

            priorFormattedString = beforeEdit;
            changeStart = start;
            removedCount = removed;

            numeralCountBeforeSelection = countNumerals(priorFormattedString.substring(0, changeStart));
            numeralCountRemoved = countNumerals(priorFormattedString.substring(changeStart, changeStart + removedCount));
        }

        /**
         * Call in onTextChanged to record new text and how many characters were added after changeStart
         *
         * @param afterEdit new string after user input
         * @param added how many characters were added (same start position as before)
         */
        public void recordChangedText(String afterEdit, int added) {
            addition = (added > 0);
            additionCount = added;
            numeralCountAdded = countNumerals(afterEdit.substring(changeStart, changeStart + additionCount));

            currentNumericalString = afterEdit.replaceAll("[^0-9]", "");
        }

        /**
         * Re-process the edit for our particular formatting needs.
         */
        public void processEdit() {
            forceRemovalPastFormatting();
            finalizeEdit();
        }

        /**
         * @return Integer value of the field after an edit.
         */
        public int getCurrentRawValue() {
            return currentRawValue;
        }

        /**
         * @return Formatted number after an edit.
         */
        public String getCurrentFormattedString() {
            return currentFormattedString;
        }

        /**
         * @return Cursor position after an edit
         */
        public int getCursorPosition() {
            int numeralPosition = numeralCountBeforeSelection + numeralCountAdded;
            return positionAfterNumeralN(currentFormattedString,numeralPosition);
        }

        /**
         * If a user deletes a value, but no numerals are deleted, then delete the numeral proceeding
         * their cursor. Otherwise, we'll just add back the formatting character.
         *
         * Assumes formatting uses a single character and not multiple formatting characters in a row.
         */
        private void forceRemovalPastFormatting() {
            if (removal && (!addition) && (numeralCountRemoved == 0)) {
                String before = currentNumericalString.substring(0, numeralCountBeforeSelection - 1);
                String after = currentNumericalString.substring(numeralCountBeforeSelection);

                currentNumericalString =  before + after;
                numeralCountRemoved++;
                numeralCountBeforeSelection--;
            }
        }

        /**
         * Determine the result of the edit, including new display value and raw value
         */
        private void finalizeEdit() {
            currentFormattedString = "";
            currentRawValue = 0;
            if (currentNumericalString.length() == 0) {
                return; // There is no entry now.
            }
            try {
                currentRawValue = Integer.parseInt(currentNumericalString);
            } catch (NumberFormatException nfe) {
                abortEdit();  // Value is not an integer, return to previous state.
                return;
            }
            currentFormattedString = String.format("%,d", currentRawValue);
        }

        /**
         * Current text, same as the old text.
         */
        private void abortEdit() {
            currentFormattedString = priorFormattedString;
            currentNumericalString = currentFormattedString.replaceAll("[^0-9]", "");
            numeralCountRemoved = 0;
            numeralCountAdded = 0;
            try {
                currentRawValue = Integer.parseInt(currentNumericalString);
            } catch (Exception e) { currentRawValue = 0; }
        }

        /**
         * Determine how many numerical characters exist in a string
         * @param s
         * @return the number of numerical characters in the string
         */
        private int countNumerals(String s) {
            String newString = s.replaceAll("[^0-9]", "");
            return newString.length();
        }

        /**
         * Determine how to place a cursor after the Nth Numeral in a formatted string.
         * @param s - Formatted string
         * @param n - The position of the cursor should follow the "Nth" number in the string
         * @return the position of the nth character in a formatted string
         */
        private int positionAfterNumeralN(String s, int n) {
            int numeralsFound = 0;

            if (n == 0) {
                return 0;
            }

            for (int i = 0; i < s.length(); i++) {
                if(s.substring(i,i+1).matches("[0-9]")) {
                    if(++numeralsFound == n) {
                        return i + 1;
                    }
                }
            }
            return s.length();
        }
    }
}

At a highlevel, what that does is:

  • Determine which numbers were actually in the string after it was edited
  • Process the edit to the numeral version of the string if the numbers weren't edited
  • Convert the numeral back to a formatted string
  • Determine, where the cursor should be based on where editing began and how much text was added

It also nicely handles edge cases like completely deleted input, integer overflow and erroneous input.

Kirk
  • 618
  • 4
  • 7
  • 21
  • I have tried this solution without the listener. The cursor still moving to the end of text each time we edited the value. Is there any effect if I implement without listener? – Selmeny Dec 10 '21 at 06:04
1

You can use a custom TextInputEditText :

public class NumberTextInputEditText extends TextInputEditText {

public NumberTextInputEditText(@NonNull Context context) {
    super(context);
}

public NumberTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public NumberTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    addTextChangedListener(textWatcher);
}

public String formatNumber(double number) {
    DecimalFormat decimalFormat = new DecimalFormat("#,###");
    return decimalFormat.format(number);
}

public TextWatcher textWatcher = new TextWatcher() {
    @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) {
        removeTextChangedListener(this);
        String text = getText().toString();
        String format = "";
        if (!TextUtils.isEmpty(text)) {
            try {
                format = formatNumber(Double.parseDouble(new BigDecimal(text.replaceAll(",", "")).toString()));
            } catch (NumberFormatException e) {
                format = "";
            }
            setText(format);
            setSelection(format.length());
        }
        addTextChangedListener(this);
    }
};}

just use it like a view in your layout:

<com.your.package.name.NumberTextInputEditText
  android:layout_width="match_parent"
  android:layout_height="wrap_content"/>
Mohsents
  • 691
  • 11
  • 9
0

Here i have tested my application code. text-watcher how to add comma in currency thousand, lake currency.

 private TextWatcher textWatcherAmount = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            String initial = s.toString();

            if (inputEdtHawalaRate == null) return;

            if (!TextUtils.isEmpty(initial)) {

                initial = initial.replace(",", "");

                NumberFormat formatter = new DecimalFormat("##,##,###");

                inputEdtHawalaRate.removeTextChangedListener(this);

                double myNumber = Double.parseDouble(initial);
                String processed = formatter.format(myNumber);

                //Assign processed text
                inputEdtHawalaRate.setText(processed);

                try {
                    inputEdtHawalaRate.setSelection(processed.length());
                } catch (Exception e) {
                    e.printStackTrace();
                }

                //Give back the listener
                inputEdtHawalaRate.addTextChangedListener(this);

            }

        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    };


 if (inputEdtHawalaRate != null) {
            inputEdtHawalaRate.addTextChangedListener(textWatcherAmount);
        } 

// getting amount on double type varaible (On textwatcher editetxt value get).

String amount = Objects.requireNonNull(inputEdtHawalaRate.getText()).toString().trim();
double hawalaAmount = 0.0;

 String[] a = amount.split(",");
            finalAmount = TextUtils.join("", a);
            hawalaAmount = Double.parseDouble(finalAmount);
0

I was looking for a locale aware solution since we have customers across the globe. So I built upon dr0pdb's answer.

Here's a TextWatcher class (in kotlin) I have created to solve this.

https://github.com/abhilashd-locus/edittext-locale-aware-thousands-separator

Features:

  • Add thousands separator dynamically as the user types
  • Enable editing in between the string and not only at the ends
  • Style of thousands separation is based upon the locale (eg: 100,000 vs 1,00,000)
  • Symbol of thousands separator and decimal marker is based on the locale (eg: 100,000.00 vs 100.000,00)
  • Supports all languages and locales

Disadvantages:

  • Does not support copy/paste operations
  • In right-to-left languages (eg. Arabic), the cursor jumps to the end on deleting the first number

.

// ThousandsSeparatorTextWatcher.kt --> add this TextWatcher to the 
// EditText you want to add the functionality of dynamic locale aware thousands separator
class ThousandsSeparatorTextWatcher(private var editText: EditText?, private val callback: TextChangedCallback) : TextWatcher {

//keeping a count of the digits before the cursor to reset the cursor at the correct place
private var digitsBeforeCursor = -1
private val thousandSeparator: Char = DecimalFormatSymbols(Locale.getDefault()).groupingSeparator
private val decimalMarker: Char = DecimalFormatSymbols(Locale.getDefault()).decimalSeparator

init {
    editText?.apply {

        addTextChangedListener(this@ThousandsSeparatorTextWatcher)

        //disabling copy/paste to avoid format and parse errors
        disableTextSelection(this)

        //diabling text selection
        isLongClickable = false
        setTextIsSelectable(false)

        //ensuring correct input type
        keyListener = DigitsKeyListener.getInstance("0123456789$decimalMarker");
    }
}

private fun disableTextSelection(editText: EditText) {

    editText.customSelectionActionModeCallback = object : android.view.ActionMode.Callback {

        override fun onActionItemClicked(mode: android.view.ActionMode?, item: MenuItem?) = false

        override fun onCreateActionMode(mode: android.view.ActionMode?, menu: Menu?) = false

        override fun onPrepareActionMode(mode: android.view.ActionMode?, menu: Menu?) = false

        override fun onDestroyActionMode(mode: android.view.ActionMode?) {}
    }
}

/***
 * We are going to calculate the number of numeric digits before the cursor when user starts editing
 * We will keep a count of this number to reset the cursor to the correct position after editing is complete
 */
override fun beforeTextChanged(sequenceBeforeEdit: CharSequence, startPos: Int, count: Int, after: Int) {

    val textBeforeEdit = sequenceBeforeEdit.toString()

    if (textBeforeEdit.isEmpty()) {
        //in an empty string, cursor position is at 1 if a character is being added (after == 1)
        //if a character is not being added, cursor position remains at the beginning
        digitsBeforeCursor = if (after == 0) -1 else 1
        return
    }

    digitsBeforeCursor = if (after == 0) {
        //if characters are being removed
        //count will always be 1 since we have disabled selection (in which case count will be equal to the number of characters selected)
        val textBeforeNewCursor = textBeforeEdit.substring(0, startPos)
        textBeforeNewCursor.count { it != thousandSeparator }
    } else {
        //if characters are being added
        //after will always be 1 since we have disabled pasting (in which case after will be equal to the number of characters being pasted)
        if (startPos == textBeforeEdit.length) {
            //if adding a character to the end of the string
            textBeforeEdit.count { it != thousandSeparator } + 1
        } else {
            //if adding a character in between the string
            val textBeforeNewCursor = textBeforeEdit.substring(0, startPos + 1)
            textBeforeNewCursor.count { it != thousandSeparator }
        }
    }
}

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

/***
 * We will get the numeric value in the editText after stripping all the formatting
 * We will then reformat this number to add the correct thousands separation and decimal marker according to the locale
 * We then set the cursor to the correct position as we calculated in beforeTextChanged()
 */
override fun afterTextChanged(editable: Editable) {

    val text = editable.toString()

    //if the EditText is cleared, trigger callback with a null value to indicate an empty field
    if (text.isEmpty()) {
        digitsBeforeCursor = -1
        callback.onChanged(null)
        return
    }

    //get the double value of the entered number
    val numberValue = getNumberFromFormattedCurrencyText(text)

    //re-format the number to get the correct separation format and symbols
    var newText = getCurrencyFormattedAmountValue(numberValue)

    //If user was inputting decimal part of the number, reformatting will return a string without decimal point.
    //So we need to add it back after the reformatting is complete
    if (text.endsWith(decimalMarker)) {
        newText += decimalMarker
    } else if (text.endsWith(decimalMarker + "0")) {
        newText += decimalMarker + "0"
    }

    //removing the listener to prevent infinite triggers
    editText?.removeTextChangedListener(this)

    //set the reformatted text
    editText?.setText(newText)

    //send the number typed to the callback
    callback.onChanged(numberValue)

    //set the cursor to the right position after reformatting the string
    if (digitsBeforeCursor != -1) {
        var numbersParsed = 0
        for (i in newText.indices) {
            if (newText[i] != thousandSeparator) {
                numbersParsed++
            }
            if (numbersParsed == digitsBeforeCursor) {
                editText?.setSelection(i + 1)
                break
            }
        }
        digitsBeforeCursor = -1
    }

    //add the listener back
    editText?.addTextChangedListener(this)
}

/***
 * Function to remove the listener and release reference to the EditText
 */
fun removeWatcherFromEditText() {
    editText?.removeTextChangedListener(this)
    editText = null
}

interface TextChangedCallback {
    fun onChanged(newNumber: Double?)
}

companion object{
    
    @JvmStatic
    fun getNumberFromFormattedCurrencyText(formattedText: String?) = formattedText?.let {
        val numberFormat = NumberFormat.getNumberInstance(Locale.getDefault())
        try {
            numberFormat.parse(it)?.toDouble()
        } catch (exception: ParseException) {
            0.0
        }
    } ?: 0.0

    @JvmStatic
    fun getCurrencyFormattedAmountValue(amount: Double?) = amount?.let {
        val numberFormat = NumberFormat.getNumberInstance(Locale.getDefault())
        numberFormat.maximumFractionDigits = 2
        numberFormat.format(amount)
    } ?: ""
}
}
0

I know it's late but maybe can help

fun generate_seprators(input: String?): String? {
        var input = input
        var result = ""
        var float_section = ""
        if (input == null) input = ""
        var temp = input.trim { it <= ' ' }
        temp = temp.replace(",", "")
        var input_array = temp.split(".")
        var decimal_section = input_array[0]
        if(input_array.size>1)
            float_section = input_array[1]
        if (decimal_section.length > 3) {
            var num = 0
            for (i in decimal_section.length downTo 1) {
                if (num == 3) {
                    num = 0
                    result = ",$result"
                }
                num++
                result = decimal_section.substring(i - 1, i) + result
            }
            if(float_section!="")
                result = "$result.$float_section"
        } else {
            result = decimal_section.replace(",", "")
            if(float_section!="")
                result = "$result.$float_section"
        }
        return result
    }
SaeidSds
  • 21
  • 1
  • 3