104

I have an editText, starting value is $0.00. When you press 1, it changes to $0.01. Press 4, it goes to $0.14. Press 8, $1.48. Press backspace, $0.14, etc.

That works, the problem is, if somebody manually positions the cursor, problems occur in the formatting. If they were to delete the decimal, it won't come back. If they put the cursor in front of the decimal and type 2, it will display $02.00 instead of $2.00. If they try to delete the $ it will delete a digit instead, for example.

Here is code I'm using, I'd appreciate any suggestions.

mEditPrice.setRawInputType(Configuration.KEYBOARD_12KEY);
    public void priceClick(View view) {
    mEditPrice.addTextChangedListener(new TextWatcher(){
        DecimalFormat dec = new DecimalFormat("0.00");
        @Override
        public void afterTextChanged(Editable arg0) {
        }
        @Override
        public void beforeTextChanged(CharSequence s, int start,
                int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start,
                int before, int count) {
            if(!s.toString().matches("^\\$(\\d{1,3}(\\,\\d{3})*|(\\d+))(\\.\\d{2})?$"))
            {
                String userInput= ""+s.toString().replaceAll("[^\\d]", "");
                if (userInput.length() > 0) {
                    Float in=Float.parseFloat(userInput);
                    float percen = in/100;
                    mEditPrice.setText("$"+dec.format(percen));
                    mEditPrice.setSelection(mEditPrice.getText().length());
                }
            }
        }
    });
Roger
  • 4,249
  • 10
  • 42
  • 60
  • 1
    Excuse my ignorance, but is this code snippet from one of the activity lifecycle methods, or are they in a custom class you made? Can you provide a more complete code sample please? Thanks! – Argus9 May 13 '13 at 18:44
  • This works for me I have tried this external lib https://android-arsenal.com/details/1/5374 – pravin maske May 04 '18 at 09:52

27 Answers27

172

I tested your method, but it fails when I use great numbers... I created this:

private String current = "";
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    if(!s.toString().equals(current)){
       [your_edittext].removeTextChangedListener(this);

       String cleanString = s.toString().replaceAll("[$,.]", "");
                
       double parsed = Double.parseDouble(cleanString);
       String formatted = NumberFormat.getCurrencyInstance().format((parsed/100));
                    
       current = formatted;
       [your_edittext].setText(formatted);
       [your_edittext].setSelection(formatted.length());
       
       [your_edittext].addTextChangedListener(this);
    }
}

Kotlin variant:

private var current: String = ""

         override fun onTextChanged(
            s: CharSequence,
            start: Int,
            before: Int,
            count: Int
        ) {
            if (s.toString() != current) {
                discount_amount_edit_text.removeTextChangedListener(this)

                val cleanString: String = s.replace("""[$,.]""".toRegex(), "")

                val parsed = cleanString.toDouble()
                val formatted = NumberFormat.getCurrencyInstance().format((parsed / 100))

                current = formatted
                discount_amount_edit_text.setText(formatted)
                discount_amount_edit_text.setSelection(formatted.length)

                discount_amount_edit_text.addTextChangedListener(this)
            }
        }
dazza5000
  • 7,075
  • 9
  • 44
  • 89
Guilherme Oliveira
  • 2,132
  • 1
  • 14
  • 20
  • 39
    Might be better to do the following rather than assume dollar symbol: `String replaceable = String.format("[%s,.]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol()); String cleanString = s.toString().replaceAll(replaceable, "");` – craigp Jun 08 '11 at 14:25
  • 6
    Hmm, actually have tried this myself now, the regex pattern from replaceAll should look like this, to handle spaces as well: `String replaceable = String.format("[%s,.\\s]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol());` – craigp Jun 08 '11 at 14:36
  • Hi, i tried the above code and got a strange exception saying this: java.lang.StackOverflowError – Jono Aug 22 '11 at 09:50
  • 6
    Isn't it recommended *not* to make changes in `onTextChanged() and rather to do so in `afterTextChanged()` – codinguser Jun 08 '12 at 08:59
  • 1
    since changing the text in onTextChanged causes an infinite loop, you should do it in `afterTextChanged` – martyglaubitz Jul 15 '12 at 18:44
  • I submitted a couple changes that replace double with BigDecimal to improve accuracy. I also added an option to move the cursor to the end of the text field. Hope everyone likes them. – Hackmodford Jun 03 '13 at 19:54
  • Guilherme can u please show me how to format it for 4 digits after a decimal? – Joyson Jul 15 '13 at 13:58
  • 3
    I am interested to know why the text changed listener is removed and then re-added every-time? For me it works if only added once (and I moved the changes to afterTextChanged) – Daniel Wilson May 21 '14 at 12:59
  • 6
    I does not work when you put 1 -> 0 -> 0 to get 1,00. Its because you get to the point in which 0,1 is changed into 010 string and 010 into `double` is 10. `10 / 100 = 0,1` you cannot get past it. – JakubW Nov 27 '14 at 12:35
  • Wow this solution worked for me but it fails when user change the cursor position for restricting cursor position http://stackoverflow.com/questions/11170409/how-to-disable-cursor-positioning-and-text-selection-in-an-edittext-android this link worked for me. – Rohit Bandil Jan 21 '16 at 07:00
  • @danielWilson it's because the text is also being changed by us and this will trigger our watcher. – Rubin Yoo Dec 16 '16 at 16:03
  • 1
    Would be better to use `String cleanString = s.toString().replaceAll("\\D", "");` rather than `String cleanString = s.toString().replaceAll("[$,.]", "");` – Deepak Goyal Apr 03 '17 at 17:12
  • 1
    how to use other money symbol than dollar? – Arnold Brown Oct 26 '18 at 04:45
  • This textwatcher is firing twice on every key hit! Any ideas why? – Rod Jan 25 '19 at 12:56
  • Why can I still move the cursor even with `mEditText.setMovementMethod(null);`. – Aliton Oliveira Feb 15 '19 at 18:25
  • The problem with this is that it always assume the dollar symbol – Cyd Dec 13 '20 at 03:55
  • In order to avoid issues with the $ symbol, it can be a good choice to call getCurrencyInstance(Locale locale) instead of getCurrencyInstance(). For the particular case of $ it can be used Locale.US – Ariel Dec 20 '20 at 04:07
  • @ArnoldBrown Other locale configuration can be used when called getCurrencyInstance() method – Ariel Dec 20 '20 at 04:09
  • @Guilherme Oliveira How to use it if i want to enter amount like 10.22 as it is not allowing to enter zero – Vajani Kishan Apr 16 '21 at 04:11
36

Based on some of the above answers I created a MoneyTextWatcher which you'd use as follows:

priceEditText.addTextChangedListener(new MoneyTextWatcher(priceEditText));

and here's the class:

public class MoneyTextWatcher implements TextWatcher {
    private final WeakReference<EditText> editTextWeakReference;

    public MoneyTextWatcher(EditText editText) {
        editTextWeakReference = new WeakReference<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 editable) {
        EditText editText = editTextWeakReference.get();
        if (editText == null) return;
        String s = editable.toString();
        if (s.isEmpty()) return;
        editText.removeTextChangedListener(this);
        String cleanString = s.replaceAll("[$,.]", "");
        BigDecimal parsed = new BigDecimal(cleanString).setScale(2, BigDecimal.ROUND_FLOOR).divide(new BigDecimal(100), BigDecimal.ROUND_FLOOR);
        String formatted = NumberFormat.getCurrencyInstance().format(parsed);
        editText.setText(formatted);
        editText.setSelection(formatted.length());
        editText.addTextChangedListener(this);
    }
}
ToddH
  • 2,801
  • 2
  • 28
  • 28
  • I have been using this for a while now but recently found a small problem, if you hold the delete button on some keyboards it deletes the whole word/group of text and causes `java.lang.NumberFormatException: Bad offset/length` – BluGeni Feb 12 '15 at 15:17
  • 1
    It worked perfectly for me! Attention to 'editText.setSelection (formatted.length ());' must be observed to property 'maxLength' instance of EditText in question. maxLength == 13; formatted.length () == 14; If 'formatted.length' is greater than 'maxLength' the following error occurs: IndexOutOfBoundsException: setSpan (14 ... 14) ends length beyond 13 tks – GFPF Apr 08 '15 at 20:22
  • 1
    @BluGeni to fix that just add an s.isEmpty check before removing the text change listener if(s.isEmpty()) return; editText.removeTextChangedListener(this); Also in the cleanString line, s.toString() is redundant – Mike Baglio Jr. Sep 10 '15 at 16:34
  • 1
    best answer than only one suggestion is to change .replaceAll("[$...) for -> .replaceAll("[^\\d.]", ""); since im other currency you have other characters than just the $, like in my case was the R$ (brazilian) – user2582318 Dec 10 '15 at 05:15
  • 1
    sorry the correct suggestion is this one -> ```.replaceAll("[^0-9]", "")```, the one above has a limit of 9.999.999 -_- – user2582318 Dec 10 '15 at 05:20
  • Take caution using this in a recyclerview. In most cases you shouldn't see an issue. Add it to an edittext in a recyclerview that has a lot of datasetchanged calls and you will get an ANR in short order. – DevinM Jul 09 '20 at 13:09
27

Here is my custom CurrencyEditText

import android.content.Context;import android.graphics.Rect;import android.text.Editable;import android.text.InputFilter;import android.text.InputType;import android.text.TextWatcher;
import android.util.AttributeSet;import android.widget.EditText;import java.math.BigDecimal;import java.math.RoundingMode;
import java.text.DecimalFormat;import java.text.DecimalFormatSymbols;
import java.util.Locale;

/**
 * Some note <br/>
 * <li>Always use locale US instead of default to make DecimalFormat work well in all language</li>
 */
public class CurrencyEditText extends android.support.v7.widget.AppCompatEditText {
    private static String prefix = "VND ";
    private static final int MAX_LENGTH = 20;
    private static final int MAX_DECIMAL = 3;
    private CurrencyTextWatcher currencyTextWatcher = new CurrencyTextWatcher(this, prefix);

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

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

    public CurrencyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
        this.setHint(prefix);
        this.setFilters(new InputFilter[] { new InputFilter.LengthFilter(MAX_LENGTH) });
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        if (focused) {
            this.addTextChangedListener(currencyTextWatcher);
        } else {
            this.removeTextChangedListener(currencyTextWatcher);
        }
        handleCaseCurrencyEmpty(focused);
    }

    /**
     * When currency empty <br/>
     * + When focus EditText, set the default text = prefix (ex: VND) <br/>
     * + When EditText lose focus, set the default text = "", EditText will display hint (ex:VND)
     */
    private void handleCaseCurrencyEmpty(boolean focused) {
        if (focused) {
            if (getText().toString().isEmpty()) {
                setText(prefix);
            }
        } else {
            if (getText().toString().equals(prefix)) {
                setText("");
            }
        }
    }

    private static class CurrencyTextWatcher implements TextWatcher {
        private final EditText editText;
        private String previousCleanString;
        private String prefix;

        CurrencyTextWatcher(EditText editText, String prefix) {
            this.editText = editText;
            this.prefix = prefix;
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // do nothing
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // do nothing
        }

        @Override
        public void afterTextChanged(Editable editable) {
            String str = editable.toString();
            if (str.length() < prefix.length()) {
                editText.setText(prefix);
                editText.setSelection(prefix.length());
                return;
            }
            if (str.equals(prefix)) {
                return;
            }
            // cleanString this the string which not contain prefix and ,
            String cleanString = str.replace(prefix, "").replaceAll("[,]", "");
            // for prevent afterTextChanged recursive call
            if (cleanString.equals(previousCleanString) || cleanString.isEmpty()) {
                return;
            }
            previousCleanString = cleanString;

            String formattedString;
            if (cleanString.contains(".")) {
                formattedString = formatDecimal(cleanString);
            } else {
                formattedString = formatInteger(cleanString);
            }
            editText.removeTextChangedListener(this); // Remove listener
            editText.setText(formattedString);
            handleSelection();
            editText.addTextChangedListener(this); // Add back the listener
        }

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

        private String formatDecimal(String str) {
            if (str.equals(".")) {
                return prefix + ".";
            }
            BigDecimal parsed = new BigDecimal(str);
            // example pattern VND #,###.00
            DecimalFormat formatter = new DecimalFormat(prefix + "#,###." + getDecimalPattern(str),
                    new DecimalFormatSymbols(Locale.US));
            formatter.setRoundingMode(RoundingMode.DOWN);
            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() - str.indexOf(".") - 1;
            StringBuilder decimalPattern = new StringBuilder();
            for (int i = 0; i < decimalCount && i < MAX_DECIMAL; i++) {
                decimalPattern.append("0");
            }
            return decimalPattern.toString();
        }

        private void handleSelection() {
            if (editText.getText().length() <= MAX_LENGTH) {
                editText.setSelection(editText.getText().length());
            } else {
                editText.setSelection(MAX_LENGTH);
            }
        }
    }
}

Use it in XML like

 <...CurrencyEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

You should edit 2 constant below for suitable for your project

private static String prefix = "VND ";
private static final int MAX_DECIMAL = 3;

enter image description here

Demo on github

Linh
  • 57,942
  • 23
  • 262
  • 279
  • 2
    This is brilliant! – YTerle Oct 03 '17 at 10:19
  • 1
    I found that after typing to reach the max number of decimal places, trying to enter a number 5-9 will increase the last decimal place by 1... it's rounding up! My fix was to call `formatter.setRoundingMode(RoundingMode.DOWN);` in the `formatDecimal` method. – B W Nov 29 '17 at 19:45
  • @bwicks thank you so much for finding the problem. I have approved your edit – Linh Nov 30 '17 at 01:19
  • how to put currency symbol instade of VND ?? – Mayur Karmur Oct 17 '18 at 13:32
  • You should move `previousCleanString = cleanString;` to after if block, and change it to this: `previousCleanString = formattedString.replace(prefix, "").replace("[,]".toRegex(), "")` Otherwise, if you have $100.00 on the screen and you tap on 0 two times, it will show $100.000. 1 digit more than `MAX_DECIMAL` – Gokhan Arik Aug 08 '19 at 00:54
  • 1
    Another improvement idea: If user enters `$.`, when we get raw value as `.` and parse to Double, it gives NFE. To fix, I made `formatDecimal()` to return `prefix + "0.";` and changed `#,###.` to `#,##0.` inside `formatDecimal()`. This also looks better when user enters decimal places only. It shows as `$0.25` instead of `$.25`. – Gokhan Arik Aug 08 '19 at 02:40
  • 1. if you put selection inside prefix it crashes, 2. it doesn't remember cursor position and jumps at the end on each digit – ildar ishalin Nov 28 '19 at 16:04
  • added tweak on "formalDecimal" method added before return is called Log.d("formatDecimal",parsed + ""); if (Float.parseFloat(parsed + "") < 1) { return prefix + "0" +formatter.format(parsed).replace(prefix,""); } this fix issue of return VND .5 it will instead return 0.5 – Muhammad Asyraf May 27 '21 at 01:09
14

Actually, the solution provided before is not working. It doesn't work if you want to enter 100.00.

Replace:

double parsed = Double.parseDouble(cleanString);
String formato = NumberFormat.getCurrencyInstance().format((parsed/100));

With:

BigDecimal parsed = new BigDecimal(cleanString).setScale(2,BigDecimal.ROUND_FLOOR).divide(new BigDecimal(100),BigDecimal.ROUND_FLOOR);                
String formato = NumberFormat.getCurrencyInstance().format(parsed);

I must say that I made some modifications for my code. The thing is that you should be using BigDecimal's

sebastianf182
  • 9,844
  • 3
  • 34
  • 66
8

I change the class with implements TextWatcher to use Brasil currency formats and adjusting cursor position when editing the value.

public class MoneyTextWatcher implements TextWatcher {

    private EditText editText;

    private String lastAmount = "";

    private int lastCursorPosition = -1;

    public MoneyTextWatcher(EditText editText) {
        super();
        this.editText = editText;
    }

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

        if (!amount.toString().equals(lastAmount)) {

            String cleanString = clearCurrencyToNumber(amount.toString());

            try {

                String formattedAmount = transformToCurrency(cleanString);
                editText.removeTextChangedListener(this);
                editText.setText(formattedAmount);
                editText.setSelection(formattedAmount.length());
                editText.addTextChangedListener(this);

                if (lastCursorPosition != lastAmount.length() && lastCursorPosition != -1) {
                    int lengthDelta = formattedAmount.length() - lastAmount.length();
                    int newCursorOffset = max(0, min(formattedAmount.length(), lastCursorPosition + lengthDelta));
                    editText.setSelection(newCursorOffset);
                }
            } catch (Exception e) {
               //log something
            }
        }
    }

    @Override
    public void afterTextChanged(Editable s) {
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        String value = s.toString();
        if(!value.equals("")){
            String cleanString = clearCurrencyToNumber(value);
            String formattedAmount = transformToCurrency(cleanString);
            lastAmount = formattedAmount;
            lastCursorPosition = editText.getSelectionStart();
        }
    }

    public static String clearCurrencyToNumber(String currencyValue) {
        String result = null;

        if (currencyValue == null) {
            result = "";
        } else {
            result = currencyValue.replaceAll("[(a-z)|(A-Z)|($,. )]", "");
        }
        return result;
    }

    public static boolean isCurrencyValue(String currencyValue, boolean podeSerZero) {
        boolean result;

        if (currencyValue == null || currencyValue.length() == 0) {
            result = false;
        } else {
            if (!podeSerZero && currencyValue.equals("0,00")) {
                result = false;
            } else {
                result = true;
            }
        }
        return result;
    }

    public static String transformToCurrency(String value) {
        double parsed = Double.parseDouble(value);
        String formatted = NumberFormat.getCurrencyInstance(new Locale("pt", "BR")).format((parsed / 100));
        formatted = formatted.replaceAll("[^(0-9)(.,)]", "");
        return formatted;
    }
}
Henrique Ho
  • 101
  • 1
  • 5
  • In this line "int newCursorOffset = max(0, min(formattedAmount.length(), lastCursorPosition + lengthDelta));" what kind of object is max and min ? – Arthur Melo May 23 '16 at 18:13
  • 2
    @ArthurMelo Its, Math.max, Math.min Thanks the code, and it looks like fail when removing the comma from the edittext. – Marcos Vasconcelos Aug 09 '16 at 15:57
7

I used the implementation Nathan Leigh referenced and Kayvan N's and user2582318's suggested regex to remove all chars except digits to create the following version:

fun EditText.addCurrencyFormatter() {

    // Reference: https://stackoverflow.com/questions/5107901/better-way-to-format-currency-input-edittext/29993290#29993290
    this.addTextChangedListener(object: TextWatcher {

        private var current = ""

        override fun afterTextChanged(s: Editable?) {
        }

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

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

            if (s.toString() != current) {
                this@addCurrencyFormatter.removeTextChangedListener(this)
                // strip off the currency symbol

                // Reference for this replace regex: https://stackoverflow.com/questions/5107901/better-way-to-format-currency-input-edittext/28005836#28005836
                val cleanString = s.toString().replace("\\D".toRegex(), "")
                val parsed = if (cleanString.isBlank()) 0.0 else cleanString.toDouble()
                // format the double into a currency format
                val formated = NumberFormat.getCurrencyInstance()
                        .format(parsed / 100)

                current = formated
                this@addCurrencyFormatter.setText(formated)
                this@addCurrencyFormatter.setSelection(formated.length)

                this@addCurrencyFormatter.addTextChangedListener(this)
            }
        }
    })

}

This is an extension function in Kotlin which adds the TextWatcher to the TextChangedListener of the EditText.

In order to use it, just:

yourEditText = (EditText) findViewById(R.id.edit_text_your_id);
yourEditText.addCurrencyFormatter()

I hope it helps.

Francisco Junior
  • 1,986
  • 18
  • 25
5

Even though there are lots of answers here, I would like to share this code that I found in here since I believe its the most robust and clean answer.

class CurrencyTextWatcher implements TextWatcher {

    boolean mEditing;

    public CurrencyTextWatcher() {
        mEditing = false;
    }

    public synchronized void afterTextChanged(Editable s) {
        if(!mEditing) {
            mEditing = true;

            String digits = s.toString().replaceAll("\\D", "");
            NumberFormat nf = NumberFormat.getCurrencyInstance();
            try{
                String formatted = nf.format(Double.parseDouble(digits)/100);
                s.replace(0, s.length(), formatted);
            } catch (NumberFormatException nfe) {
                s.clear();
            }

            mEditing = false;
        }
    }

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

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

}

hope it helps.

Kayvan N
  • 8,108
  • 6
  • 29
  • 38
  • Wouldn't that strip out the decimal point? So then you wouldn't be able to tell the difference between $100.00 and $10,000 - unless I'm missing something. – nasch Aug 24 '15 at 17:19
  • 2
    this is perfect answer! worked for me. my, just think about how much time I spent on those answers and finally scrolled down to bottom and found the one I want. – Ge Rong Oct 29 '15 at 08:19
  • I'm glad it helped you. – Kayvan N Nov 15 '16 at 19:07
  • @nasch This is a TextWatcher and it formats the text as user types which prevents the case you mention. – Kayvan N Nov 15 '16 at 19:13
  • @KayvanN I know what a TextWatcher is. `replaceAll("\\D", "")` will remove everything that isn't a digit, so "$100.00" and "$10,000" both become "10000". It seems that you're counting on the input to include cents. So if that's guaranteed, great but if not I think there are going to be problems. – nasch Nov 15 '16 at 22:50
  • @nasch you are correct but my point was that since the input gets formatted as user types, its impossible for user to type in 1 with out it becoming 0.01 or 10000 without it becoming 100.00 automatically. So your scenario would not happen. (unless user paste the value from somewhere else) – Kayvan N Nov 15 '16 at 23:44
4

Ok, here is a better way to deal with Currency formats, delete-backward keystroke. The code is based on @androidcurious' code above... But, deals with some problems related to backwards-deletion and some parse exceptions: http://miguelt.blogspot.ca/2013/01/textwatcher-for-currency-masksformatting.html [UPDATE] The previous solution had some problems... This is a better solutoin: http://miguelt.blogspot.ca/2013/02/update-textwatcher-for-currency.html And... here are the details:

This approach is better since it uses the conventional Android mechanisms. The idea is to format values after the user exists the View.

Define an InputFilter to restrict the numeric values – this is required in most cases because the screen is not large enough to accommodate long EditText views. This can be a static inner class or just another plain class:

/** Numeric range Filter. */
class NumericRangeFilter implements InputFilter {
    /** Maximum value. */
    private final double maximum;
    /** Minimum value. */
    private final double minimum;
    /** Creates a new filter between 0.00 and 999,999.99. */
    NumericRangeFilter() {
        this(0.00, 999999.99);
    }
    /** Creates a new filter.
     * @param p_min Minimum value.
     * @param p_max Maximum value. 
     */
    NumericRangeFilter(double p_min, double p_max) {
        maximum = p_max;
        minimum = p_min;
    }
    @Override
    public CharSequence filter(
            CharSequence p_source, int p_start,
            int p_end, Spanned p_dest, int p_dstart, int p_dend
    ) {
        try {
            String v_valueStr = p_dest.toString().concat(p_source.toString());
            double v_value = Double.parseDouble(v_valueStr);
            if (v_value<=maximum && v_value>=minimum) {
                // Returning null will make the EditText to accept more values.
                return null;
            }
        } catch (NumberFormatException p_ex) {
            // do nothing
        }
        // Value is out of range - return empty string.
        return "";
    }
}

Define a class (inner static or just a class) that will implement View.OnFocusChangeListener. Note that I'm using an Utils class - the implementation can be found at "Amounts, Taxes".

/** Used to format the amount views. */
class AmountOnFocusChangeListener implements View.OnFocusChangeListener {
    @Override
    public void onFocusChange(View p_view, boolean p_hasFocus) {
        // This listener will be attached to any view containing amounts.
        EditText v_amountView = (EditText)p_view;
        if (p_hasFocus) {
            // v_value is using a currency mask - transfor over to cents.
            String v_value = v_amountView.getText().toString();
            int v_cents = Utils.parseAmountToCents(v_value);
            // Now, format cents to an amount (without currency mask)
            v_value = Utils.formatCentsToAmount(v_cents);
            v_amountView.setText(v_value);
            // Select all so the user can overwrite the entire amount in one shot.
            v_amountView.selectAll();
        } else {
            // v_value is not using a currency mask - transfor over to cents.
            String v_value = v_amountView.getText().toString();
            int v_cents = Utils.parseAmountToCents(v_value);
            // Now, format cents to an amount (with currency mask)
            v_value = Utils.formatCentsToCurrency(v_cents);
            v_amountView.setText(v_value);
        }
    }
}

This class will remove the currency format when editing - relying on standard mechanisms. When the user exits, the currency format is re-applied.

It's better to define some static variables to minimize the number of instances:

   static final InputFilter[] FILTERS = new InputFilter[] {new NumericRangeFilter()};
   static final View.OnFocusChangeListener ON_FOCUS = new AmountOnFocusChangeListener();

Finally, within the onCreateView(...):

   EditText mAmountView = ....
   mAmountView.setFilters(FILTERS);
   mAmountView.setOnFocusChangeListener(ON_FOCUS);

You can reuse FILTERS and ON_FOCUS on any number of EditText views.

Here is the Utils class:

public class Utils {

   private static final NumberFormat FORMAT_CURRENCY = NumberFormat.getCurrencyInstance();
   /** Parses an amount into cents.
    * @param p_value Amount formatted using the default currency. 
    * @return Value as cents.
    */
   public static int parseAmountToCents(String p_value) {
       try {
           Number v_value = FORMAT_CURRENCY.parse(p_value);
           BigDecimal v_bigDec = new BigDecimal(v_value.doubleValue());
           v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
           return v_bigDec.movePointRight(2).intValue();
       } catch (ParseException p_ex) {
           try {
               // p_value doesn't have a currency format.
               BigDecimal v_bigDec = new BigDecimal(p_value);
               v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
               return v_bigDec.movePointRight(2).intValue();
           } catch (NumberFormatException p_ex1) {
               return -1;
           }
       }
   }
   /** Formats cents into a valid amount using the default currency.
    * @param p_value Value as cents 
    * @return Amount formatted using a currency.
    */
   public static String formatCentsToAmount(int p_value) {
       BigDecimal v_bigDec = new BigDecimal(p_value);
       v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
       v_bigDec = v_bigDec.movePointLeft(2);
       String v_currency = FORMAT_CURRENCY.format(v_bigDec.doubleValue());
       return v_currency.replace(FORMAT_CURRENCY.getCurrency().getSymbol(), "").replace(",", "");
   }
   /** Formats cents into a valid amount using the default currency.
    * @param p_value Value as cents 
    * @return Amount formatted using a currency.
    */
   public static String formatCentsToCurrency(int p_value) {
       BigDecimal v_bigDec = new BigDecimal(p_value);
       v_bigDec = v_bigDec.setScale(2, BigDecimal.ROUND_HALF_UP);
       v_bigDec = v_bigDec.movePointLeft(2);
       return FORMAT_CURRENCY.format(v_bigDec.doubleValue());
   }

}
Patrick
  • 1,717
  • 7
  • 21
  • 28
miguelt
  • 384
  • 4
  • 8
  • Whilst this may theoretically answer the question, we would like you to include the essential parts of the linked article in your answer, and provide the [link for reference](http://meta.stackexchange.com/q/8259). Failing to do that leaves the answer at risk from link rot. – Kev Feb 27 '13 at 14:27
  • I'm getting java.lang.NumberFormatException: Invalid double: "$12,345.00" when the edit text losts focus. How to fix it. – Madhan Sep 28 '16 at 10:02
4

I built on Guilhermes answer, but I preserve the position of the cursor and also treat the periods differently - this way if a user is typing after the period, it does not affect the numbers before the period I find that this gives a very smooth input.

    [yourtextfield].addTextChangedListener(new TextWatcher()
    {
        NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
        private String current = "";

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count)
        {
            if(!s.toString().equals(current))
            {
                   [yourtextfield].removeTextChangedListener(this);

                   int selection = [yourtextfield].getSelectionStart();


                   // We strip off the currency symbol
                   String replaceable = String.format("[%s,\\s]", NumberFormat.getCurrencyInstance().getCurrency().getSymbol());
                   String cleanString = s.toString().replaceAll(replaceable, "");

                   double price;

                   // Parse the string                     
                   try
                   {
                       price = Double.parseDouble(cleanString);
                   }
                   catch(java.lang.NumberFormatException e)
                   {
                       price = 0;
                   }

                   // If we don't see a decimal, then the user must have deleted it.
                   // In that case, the number must be divided by 100, otherwise 1
                   int shrink = 1;
                   if(!(s.toString().contains(".")))
                   {
                       shrink = 100;
                   }

                   // Reformat the number
                   String formated = currencyFormat.format((price / shrink));

                   current = formated;
                   [yourtextfield].setText(formated);
                   [yourtextfield].setSelection(Math.min(selection, [yourtextfield].getText().length()));

                   [yourtextfield].addTextChangedListener(this);
                }
        }


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

        }


        @Override
        public void afterTextChanged(Editable s)
        {
        }
    });
pumpkinpie65
  • 960
  • 2
  • 14
  • 16
genixpro
  • 503
  • 1
  • 5
  • 8
3

I got this from here and changed it to comply with Portuguese currency format.

import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;

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

public class CurrencyTextWatcher implements TextWatcher {

    private String current = "";
    private int index;
    private boolean deletingDecimalPoint;
    private final EditText currency;

    public CurrencyTextWatcher(EditText p_currency) {
        currency = p_currency;
    }


    @Override
    public void beforeTextChanged(CharSequence p_s, int p_start, int p_count, int p_after) {

        if (p_after>0) {
                index = p_s.length() - p_start;
            } else {
                index = p_s.length() - p_start - 1;
            }
            if (p_count>0 && p_s.charAt(p_start)==',') {
                deletingDecimalPoint = true;
            } else {
                deletingDecimalPoint = false;
            }

    }

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

    }

    @Override
    public void afterTextChanged(Editable p_s) {


         if(!p_s.toString().equals(current)){
                currency.removeTextChangedListener(this);
                if (deletingDecimalPoint) {
                    p_s.delete(p_s.length()-index-1, p_s.length()-index);
                }
                // Currency char may be retrieved from  NumberFormat.getCurrencyInstance()
                String v_text = p_s.toString().replace("€","").replace(",", "");
                v_text = v_text.replaceAll("\\s", "");
                double v_value = 0;
                if (v_text!=null && v_text.length()>0) {
                    v_value = Double.parseDouble(v_text);
                }
                // Currency instance may be retrieved from a static member.
                NumberFormat numberFormat = NumberFormat.getCurrencyInstance(new Locale("pt", "PT"));
                String v_formattedValue = numberFormat.format((v_value/100));
                current = v_formattedValue;
                currency.setText(v_formattedValue);
                if (index>v_formattedValue.length()) {
                    currency.setSelection(v_formattedValue.length());
                } else {
                    currency.setSelection(v_formattedValue.length()-index);
                }
                // include here anything you may want to do after the formatting is completed.
                currency.addTextChangedListener(this);
             }
    }

}

The layout.xml

<EditText
    android:id="@+id/edit_text_your_id"
    ...
    android:text="0,00 €"
    android:inputType="numberDecimal"
    android:digits="0123456789" />

Get it to work

    yourEditText = (EditText) findViewById(R.id.edit_text_your_id);
    yourEditText.setRawInputType(Configuration.KEYBOARD_12KEY);
    yourEditText.addTextChangedListener(new CurrencyTextWatcher(yourEditText));
2

For me it worked like this

 public void onTextChanged(CharSequence s, int start,
                    int before, int count) {
                if(!s.toString().matches("^\\$(\\d{1,3}(\\,\\d{3})*|(\\d+))(\\.\\d{2})?$"))
                {
                    String userInput= ""+s.toString().replaceAll("[^\\d]", "");
                    if (userInput.length() > 2) {
                        Float in=Float.parseFloat(userInput);
                        price = Math.round(in); // just to get an Integer
                        //float percen = in/100;
                        String first, last;
                        first = userInput.substring(0, userInput.length()-2);
                        last = userInput.substring(userInput.length()-2);
                        edEx1.setText("$"+first+"."+last);
                        Log.e(MainActivity.class.toString(), "first: "+first + " last:"+last);
                        edEx1.setSelection(edEx1.getText().length());
                    }
                }
            }
Fernando
  • 21
  • 1
2

It is better to use InputFilter interface. Much easier to handle any kind of inputs by using regex. My solution for currency input format:

public class CurrencyFormatInputFilter implements InputFilter {

Pattern mPattern = Pattern.compile("(0|[1-9]+[0-9]*)(\\.[0-9]{1,2})?");

@Override
public CharSequence filter(
        CharSequence source,
        int start,
        int end,
        Spanned dest,
        int dstart,
        int dend) {

String result = 
        dest.subSequence(0, dstart)
        + source.toString() 
        + dest.subSequence(dend, dest.length());

Matcher matcher = mPattern.matcher(result);

if (!matcher.matches()) return dest.subSequence(dstart, dend);

return null;
}
}

Valid: 0.00, 0.0, 10.00, 111.1
Invalid: 0, 0.000, 111, 10, 010.00, 01.0

How to use:

editText.setFilters(new InputFilter[] {new CurrencyFormatInputFilter()});
Mussa
  • 1,463
  • 21
  • 25
1

I used this to allow the user to enter the currency and to convert it from string into int to store in db and to change back from int into string again

https://github.com/nleigh/Restaurant/blob/master/Restaurant/src/uk/co/nathanleigh/restaurant/CurrencyFormat.java

  • Could you include main concepts in your answer? Otherwise this pretty much is a link-only answer and is prone to deletion... – Alexander Vogt May 01 '15 at 19:19
1

If your json currency field is of number type (and not String) it may come as 3.1 , 3.15 or just 3. Because json automatically round number fields.

In this case you may need to round it for proper display (and to be able to use a mask on a input field later):

    NumberFormat nf = NumberFormat.getCurrencyInstance();

    float value = 200 // it can be 200, 200.3 or 200.37, BigDecimal will take care
    BigDecimal valueAsBD = BigDecimal.valueOf(value);
    valueAsBD.setScale(2, BigDecimal.ROUND_HALF_UP);

    String formated = nf.format(valueAsBD);

Why this is needed?

All answers point to removing currency simbols when typing judcing you are receiving the cents and so formating dolar + cents / 100 = dolar,cents. But if your json currency field is an number type (and not an String) it will round your cents, it may be 3 , 3.1 or 3.15.

Renato Probst
  • 5,914
  • 2
  • 42
  • 45
1

After looking at most of the StackOverflow posts on different ways to achieve this using a TextWatcher, InputFilter, or library like CurrencyEditText I've settled on this simple solution using an OnFocusChangeListener.

The logic is to parse the EditText to a number when it is focused and to format it back when it loses focus.

amount.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View view, boolean hasFocus) {
            Number numberAmount = 0f;
            try {
                numberAmount = Float.valueOf(amount.getText().toString());
            } catch (NumberFormatException e1) {
                e1.printStackTrace();
                try {
                    numberAmount = NumberFormat.getCurrencyInstance().parse(amount.getText().toString());
                } catch (ParseException e2) {
                    e2.printStackTrace();
                }
            }
            if (hasFocus) {
                amount.setText(numberAmount.toString());
            } else {
                amount.setText(NumberFormat.getCurrencyInstance().format(numberAmount));
            }
        }
    });
Abtin Gramian
  • 1,630
  • 14
  • 13
1

Another approach, but based on Guilherme answer. This approach is useful when your country locale is not available or if you want to use custom currency symbols. This implementation is only for positive non-decimal.

this code is in Kotlin, first, delegate setMaskingMoney for EditText

fun EditText.setMaskingMoney(currencyText: String) {
    this.addTextChangedListener(object: MyTextWatcher{
        val editTextWeakReference: WeakReference<EditText> = WeakReference<EditText>(this@setMaskingMoney)
        override fun afterTextChanged(editable: Editable?) {
            val editText = editTextWeakReference.get() ?: return
            val s = editable.toString()
            editText.removeTextChangedListener(this)
            val cleanString = s.replace("[Rp,. ]".toRegex(), "")
            val newval = currencyText + cleanString.monetize()

            editText.setText(newval)
            editText.setSelection(newval.length)
            editText.addTextChangedListener(this)
        }
    })
}

Then MyTextWatcher interface should be extended from TextWatcher. Since we only need the afterTextChanged method, the other methods need to override in this interface.

interface MyTextWatcher: TextWatcher {
    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
    override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}

and the monetize methods is:

fun String.monetize(): String = if (this.isEmpty()) "0"
    else DecimalFormat("#,###").format(this.replace("[^\\d]".toRegex(),"").toLong())

Full implementations:

fun EditText.setMaskingMoney(currencyText: String) {
    this.addTextChangedListener(object: MyTextWatcher{
        val editTextWeakReference: WeakReference<EditText> = WeakReference<EditText>(this@setMaskingMoney)
        override fun afterTextChanged(editable: Editable?) {
            val editText = editTextWeakReference.get() ?: return
            val s = editable.toString()
            editText.removeTextChangedListener(this)
            val cleanString = s.replace("[Rp,. ]".toRegex(), "")
            val newval = currencyText + cleanString.monetize()

            editText.setText(newval)
            editText.setSelection(newval.length)
            editText.addTextChangedListener(this)
        }
    })
}

interface MyTextWatcher: TextWatcher {
    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
    override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}


fun String.monetize(): String = if (this.isEmpty()) "0"
    else DecimalFormat("#,###").format(this.replace("[^\\d]".toRegex(),"").toLong())

and somewhere on onCreate method:

yourTextView.setMaskingMoney("Rp. ")
Emad Razavi
  • 1,903
  • 2
  • 17
  • 24
Hayi Nukman
  • 1,191
  • 12
  • 29
1

After too much search and fails with Doubles, BigDecimals and so on, I have made this code. It works plug And Play. Its in kotlin. So, to help others stucked like me, lets go.

The code basically is a function that will place a textWatcher and adjust the coma to the right place.

First, create this function:

fun CurrencyWatcher( editText:EditText) {

    editText.addTextChangedListener(object : TextWatcher {
        //this will prevent the loop
        var changed: Boolean = false

        override fun afterTextChanged(p0: Editable?) {
            changed = false

        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            editText.setSelection(p0.toString().length)
        }

        @SuppressLint("SetTextI18n")
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            if (!changed) {
                changed = true

                var str: String = p0.toString().replace(",", "").trim()
                var element0: String = str.elementAt(0).toString()
                var element1: String = "x"
                var element2: String = "x"
                var element3: String = "x"
                var element4: String = "x"
                var element5: String = "x"
                var element6: String = "x"

                //this variables will store each elements of the initials data for the case we need to move this numbers like: 0,01 to 0,11 or 0,11 to 0,01
                if (str.length >= 2) {
                    element1 = str.elementAt(1).toString()
                }
                if (str.length >= 3) {
                    element2 = str.elementAt(2).toString()
                }

                editText.removeTextChangedListener(this)


                //this first block of code will take care of the case
                //where the number starts with 0 and needs to adjusta the 0 and the "," place
                if (str.length == 1) {
                    str = "0,0" + str
                    editText.setText(str)

                } else if (str.length <= 3 && str == "00") {

                    str = "0,00"
                    editText.setText(str)
                    editText.setSelection(str.length)
                } else if (element0 == "0" && element1 == "0" && element2 == "0") {
                    str = str.replace("000", "")
                    str = "0,0" + str
                    editText.setText(str)
                } else if (element0 == "0" && element1 == "0" && element2 != "0") {
                    str = str.replace("00", "")
                    str = "0," + str
                    editText.setText(str)
                } else {

                    //This block of code works with the cases that we need to move the "," only because the value is bigger
                    //lets get the others elements
                    if (str.length >= 4) {
                        element3 = str.elementAt(3).toString()
                    }
                    if (str.length >= 5) {
                        element4 = str.elementAt(4).toString()
                    }
                    if (str.length >= 6) {
                        element5 = str.elementAt(5).toString()
                    }
                    if (str.length == 7) {
                        element6 = str.elementAt(6).toString()
                    }


                    if (str.length >= 4 && element0 != "0") {

                        val sb: StringBuilder = StringBuilder(str)
                        //set the coma in right place
                        sb.insert(str.length - 2, ",")
                        str = sb.toString()
                    }

                    //change the 0,11 to 1,11
                    if (str.length == 4 && element0 == "0") {

                        val sb: StringBuilder = StringBuilder(str)
                        //takes the initial 0 out
                        sb.deleteCharAt(0);
                        str = sb.toString()

                        val sb2: StringBuilder = StringBuilder(str)
                        sb2.insert(str.length - 2, ",")
                        str = sb2.toString()
                    }

                    //this will came up when its like 11,11 and the user delete one, so it will be now 1,11
                    if (str.length == 3 && element0 != "0") {
                        val sb: StringBuilder = StringBuilder(str)
                        sb.insert(str.length - 2, ",")
                        str = sb.toString()
                    }

                    //came up when its like 0,11 and the user delete one, output will be 0,01
                    if (str.length == 2 && element0 == "0") {
                        val sb: StringBuilder = StringBuilder(str)
                        //takes 0 out
                        sb.deleteCharAt(0);
                        str = sb.toString()

                        str = "0,0" + str

                    }

                    //came up when its 1,11 and the user delete, output will be 0,11
                    if (str.length == 2 && element0 != "0") {
                        val sb: StringBuilder = StringBuilder(str)
                        //retira o 0 da frente
                        sb.insert(0, "0,")
                        str = sb.toString()

                    }


                    editText.setText(str)
                }

                //places the selector at the end to increment the number
                editText.setSelection(str.length)
                editText.addTextChangedListener(this)
            }

        }
    })
}

And then you call this function this way

val etVal:EditText = findViewById(R.id.etValue)

CurrencyWatcher(etVal)
Thiago Silva
  • 670
  • 6
  • 18
0

I've implemented a Kotlin + Rx version.

It's for brazilian's currency (e.g. 1,500.00 - 5,21 - 192,90) but you can easily adapt for other formats.

Hope someone else finds it helpful.

RxTextView
            .textChangeEvents(fuel_price) // Observe text event changes
            .filter { it.text().isNotEmpty() } // do not accept empty text when event first fires
            .flatMap {
                val onlyNumbers = Regex("\\d+").findAll(it.text()).fold(""){ acc:String,it:MatchResult -> acc.plus(it.value)}
                Observable.just(onlyNumbers)
            }
            .distinctUntilChanged()
            .map { it.trimStart('0') }
            .map { when (it.length) {
                        1-> "00"+it
                        2-> "0"+it
                        else -> it }
            }
            .subscribe {
                val digitList = it.reversed().mapIndexed { i, c ->
                    if ( i == 2 ) "${c},"
                    else if ( i < 2 ) c
                    else if ( (i-2)%3==0 ) "${c}." else c
                }

                val currency = digitList.reversed().fold(""){ acc,it -> acc.toString().plus(it) }
                fuel_price.text = SpannableStringBuilder(currency)
                fuel_price.setSelection(currency.length)
            }
Vinicius Lima
  • 1,601
  • 1
  • 12
  • 15
0

Here is how I was able to display a currency in an EditText that was easy to implement and works well for the user without the potential for crazy symbols all over the place. This will not try to do any formatting until the EditText no longer has focus. The user can still go back and make any edits without jeopardizing the formatting. I use the 'formattedPrice' variable for display only, and the 'itemPrice' variable as the value that I store/use for calculations.

It seems to be working really well, but I've only been at this for a few weeks, so any constructive criticism is absolutely welcome!

The EditText view in the xml has the following attribute:

android:inputType="numberDecimal"

Global variables:

private String formattedPrice;
private int itemPrice = 0;

In the onCreate method:

EditText itemPriceInput = findViewById(R.id.item_field_price);

itemPriceInput.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        String priceString = itemPriceInput.getText().toString();

        if (! priceString.equals("")) {
            itemPrice = Double.parseDouble(priceString.replaceAll("[$,]", ""));
            formattedPrice = NumberFormat.getCurrencyInstance().format(itemPrice);
            itemPriceInput.setText(formattedPrice);
        }
    }
});
Kat
  • 1
0

In case someone is interested in a way of doing it using RxBinding and Kotlin:

var isEditing = false

RxTextView.textChanges(dollarValue)
            .filter { !isEditing }
            .filter { it.isNotBlank() }
            .map { it.toString().filter { it.isDigit() } }
            .map { BigDecimal(it).setScale(2, BigDecimal.ROUND_FLOOR).divide(100.toBigDecimal(), BigDecimal.ROUND_FLOOR) }
            .map { NumberFormat.getCurrencyInstance(Locale("pt", "BR")).format(it) }
            .subscribe {
                isEditing = true
                dollarValue.text = SpannableStringBuilder(it)
                dollarValue.setSelection(it.length)
                isEditing = false
            }
Gui Silva
  • 1,341
  • 15
  • 18
0

just an additional comment to the approved answer. You may get a crash when moving the cursor on edittext field due to parsing. I did a try catch statement, but implement your own code.

@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
        if(!s.toString().equals(current)){
        amountEditText.removeTextChangedListener(this);

            String cleanString = s.toString().replaceAll("[$,.]", "");

            try{
                double parsed = Double.parseDouble(cleanString);
                String formatted = NumberFormat.getCurrencyInstance().format((parsed/100));
                current = formatted;
                amountEditText.setText(formatted);
                amountEditText.setSelection(formatted.length());
            } catch (Exception e) {

            }

            amountEditText.addTextChangedListener(this);
        }
    }
Hareesh Sivasubramanian
  • 1,265
  • 7
  • 17
  • 27
0

you can use these methods

import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
import android.widget.TextView
import java.text.NumberFormat
import java.util.*

fun TextView.currencyFormat() {
    addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {}

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

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            removeTextChangedListener(this)
            text = if (s?.toString().isNullOrBlank()) {
                ""
            } else {
                s.toString().currencyFormat()
            }
            if(this@currencyFormat is EditText){
                setSelection(text.toString().length)
            }
            addTextChangedListener(this)
        }
    })
}

fun String.currencyFormat(): String {
    var current = this
    if (current.isEmpty()) current = "0"
    return try {
        if (current.contains('.')) {
            NumberFormat.getNumberInstance(Locale.getDefault()).format(current.replace(",", "").toDouble())
        } else {
            NumberFormat.getNumberInstance(Locale.getDefault()).format(current.replace(",", "").toLong())
        }
    } catch (e: Exception) {
        "0"
    }
}
Kourosh
  • 2,239
  • 13
  • 18
0

Kotlin version:

    var current = ""

    editText.addTextChangedListener(object: TextWatcher {
        override fun afterTextChanged(s: Editable?) {}
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            val stringText = s.toString()

            if(stringText != current) {
                editText.removeTextChangedListener(this)

                val locale: Locale = Locale.UK
                val currency = Currency.getInstance(locale)
                val cleanString = stringText.replace("[${currency.symbol},.]".toRegex(), "")
                val parsed = cleanString.toDouble()
                val formatted = NumberFormat.getCurrencyInstance(locale).format(parsed / 100)

                current = formatted
                editText.setText(formatted)
                editText.setSelection(formatted.length)
                editText.addTextChangedListener(this)
            }
        }
    })
Adriatik Gashi
  • 332
  • 4
  • 10
0
public class MoneyEditText extends android.support.v7.widget.AppCompatEditText{
public MoneyEditText(Context context) {
    super(context);
    addTextChangedListener(MoneySplitter());
}
public MoneyEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
    addTextChangedListener(MoneySplitter());
}
public MoneyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    addTextChangedListener(MoneySplitter());
}
public TextWatcher MoneySplitter() {
    TextWatcher textWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            try
            {
                removeTextChangedListener(this);
                String value = s.toString();
                if (!value.equals(""))
                {
                        if(!TextUtils.isEmpty(value))
                            setText(formatPrice(Double.parseDouble(value)));
                        setSelection(getText().toString().length());

                }
                addTextChangedListener(this);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
                addTextChangedListener(this);
            }
        }
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void afterTextChanged(Editable s) {
        }
    };
    return textWatcher;
}

public static String formatPrice(double value){
        int DecimalPointNumber = 2;
        Locale locale = Locale.getDefault();
        DecimalFormat myFormatter = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
        StringBuilder sb = new StringBuilder();
        if(DecimalPointNumber>0){
            for (int i = 0; i < DecimalPointNumber; i++) {
                sb.append("#");
            }
            myFormatter.applyPattern("###,###."+ sb.toString());
        }else
            myFormatter.applyPattern("###,###"+ sb.toString());

            return Currency.getInstance(Locale.getDefault()).getSymbol() + myFormatter.format(value);
    }
}

and then use this block as your editText

   <MoneyEditText
   android:id="@+id/txtPrice"
   android:layout_width="match_parent"
   android:layout_height="64dp"
   android:digits="0123456789.,"
   android:inputType="numberDecimal"
   android:selectAllOnFocus="true"
   android:singleLine="true" />
Saeid Mohammadi
  • 319
  • 1
  • 2
  • 11
0

This is like Saeid Mohammadi answer but I changed to accept negative numbers.

  package com.example.liberdade.util
    
    import android.text.Editable
    import android.text.TextWatcher
    import android.widget.EditText
    import java.lang.ref.WeakReference
    import java.math.BigDecimal
    import java.text.NumberFormat
    import java.util.*
    
    
    class MoneyTextWatcher : TextWatcher {
    
    
    
        private val editTextWeakReference: WeakReference<EditText?>?
        private val locale: Locale = Locale("pt", "BR")
        //private final Locale locale;
    
        constructor(editText: EditText?, locale: Locale?) {
            editTextWeakReference = WeakReference<EditText?>(editText)
            //this.locale = if (locale != null) locale else Locale.getDefault()
        }
    
        constructor(editText: EditText?) {
            editTextWeakReference = WeakReference<EditText?>(editText)
            //locale = Locale.getDefault()
        }
    
        override fun beforeTextChanged(
            s: CharSequence?,
            start: Int,
            count: Int,
            after: Int
        ) {
        }
    
        override fun onTextChanged(
            s: CharSequence?,
            start: Int,
            before: Int,
            count: Int
        ) {
        }
    
        override fun afterTextChanged(editable: Editable?) {
            val editText: EditText = editTextWeakReference?.get() ?: return
            editText.removeTextChangedListener(this)
    
            var isNegative = false
            var editableString = editable.toString()
            if (editable != null) {
                if (editableString.contains('-')) {
                    isNegative = true
                    if (editable != null) {
                        editableString = editableString.replace("-","")
                    }
                }
            }
    
            val parsed: BigDecimal? = parseToBigDecimal(editableString, locale)
            //val parsed: BigDecimal? = parseToBigDecimal(editable.toString(), locale)
            var formatted: String = NumberFormat.getCurrencyInstance(locale).format(parsed)
    
            if (isNegative && !(formatted.equals("R\$ 0,00") || formatted.equals("-R\$ 0,00"))) formatted = "-${formatted}"
            editText.setText(formatted)
            editText.setSelection(formatted.length)
            editText.addTextChangedListener(this)
        }
    
        private fun parseToBigDecimal(value: String?, locale: Locale?): BigDecimal? {
            val replaceable = java.lang.String.format(
                "[%s,.\\s]",
                NumberFormat.getCurrencyInstance(locale).currency.symbol
            )
            val cleanString = value!!.replace(replaceable.toRegex(), "")
            return BigDecimal(cleanString).setScale(
                2, BigDecimal.ROUND_FLOOR
            ).divide(
                BigDecimal(100), BigDecimal.ROUND_FLOOR
            )
        }
    }
    
    //como invocar
    //binding.editTextValorCaixa.addTextChangedListener(MoneyTextWatcher(binding.editTextValorCaixa, Locale("pt", "BR")))
Michel Fernandes
  • 1,187
  • 9
  • 8
0

Use this inputType in your XML

android:inputType="numberSigned|numberDecimal"

Add this nice Kotlin Extension function:

**
 * Use this function from [TextWatcher.afterTextChanged] it will first call [AppCompatEditText.removeTextChangedListener]
 * on the TextWatcher you pass, manipulate the text and then register it again after it call setText.
 *
 * @param fallback The String that we will return if the user is doing illegal adding, like trying to add a third digit after the comma.
 *                  It is best if you will keep the fallback as a member of the class the EditText resides in - and store in it
 * @param textWatcher [TextWatcher] It will be used to unregister before manipulating the text.
 * @param locale The locale that we will pass to [NumberFormat.getCurrencyInstance] - it will affect the currency sign. default is [Locale.US]
 *
 * @return A formatted String to use in [AppCompatEditText.setText]
 *
 */
fun AppCompatEditText.formatCurrency(@NonNull fallback: String, @NonNull textWatcher: TextWatcher,
                                     locale: Locale = Locale.US) {

    removeTextChangedListener(textWatcher)

    var original = text.toString()
    if (original.startsWith(".")) {
        // If the user press on '.-' key on the beginning of the amount - we are getting '.' and we turn it into '-'
        setText(original.replaceFirst(".", "-"))
        addTextChangedListener(textWatcher)
        setSelection(text?.length ?: 0)
        return
    }
    val split = original.split(".")
    when (split.size) {
        0 -> {
            setText(fallback)
            addTextChangedListener(textWatcher)
            setSelection(text?.length ?: 0)
            return
        }
        1 -> {
            if (split[0] == "-") {
                setText("-")
                addTextChangedListener(textWatcher)
                setSelection(text?.length ?: 0)
                return
            }
        }
        2 -> {
            if (split[1].length > 2) {
                setText(fallback)
                addTextChangedListener(textWatcher)
                setSelection(text?.length ?: 0)
                return
            }
        }
    }
    // We store the decimal value in a local variable
    val decimalSplit = original.split(".")
    // flag to indicate that we have a decimal part on the original String.
    val hasDecimal = decimalSplit.size > 1
    if (hasDecimal) {
        original = decimalSplit[0]
    }
    val isNegative = original.startsWith("-")
    val cleanString: String = original.replace("""[$,]""".toRegex(), "")

    val result = if (cleanString.isNotEmpty() && cleanString != "-") {
        val formatString = original.replace("""[-$,.]""".toRegex(), "")
        // Add Commas and Currency symbol.
        var result = NumberFormat.getCurrencyInstance(locale).format(formatString.toDouble())
        result = result.split('.')[0]
        if (isNegative) {
            // If it was negative we must add the minus sign.
            result = "-${result}"
        }
        if (hasDecimal) {
            // after the formatting the decimal is omitted, we need to append it.
            result = "${result}.${decimalSplit[1]}"
        }
        result
    } else {
        original
    }
    setText(result)
    addTextChangedListener(textWatcher)
    setSelection(text?.length ?: 0)
}

And use it like this:

class MyCoolClass{
  
    private var mLastAmount = ""
    ...
    ...

    private fun addTextWatcherToEt() {
            mEtAmount.addTextChangedListener(object : TextWatcher {
    
                override fun afterTextChanged(s: Editable?) {
                    mEtAmount.formatCurrency(mLastAmount, this)
                    mLastAmount = mEtAmount.text.toString()
                    
                    // Do More stuff here if you need...
                }
    
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                }
    
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    
                }
            })
        }

}
Gal Rom
  • 6,221
  • 3
  • 41
  • 33
0

I think this library is better, because :

  1. it support middle number editing without set cursor to the end
  2. we can easily change the currency sign
  3. prevent deleting currency sign
  4. auto delete "0" in the beginning
  5. and other costumization

enter image description here

zihadrizkyef
  • 1,849
  • 3
  • 23
  • 46