Friday, August 24, 2012

Android EditText with number format input

Some applications require formatting the EditText's value while typing. I.E., a number that needs to be formatted with decimal and thousands separators.

We've created a little helper class implementing TextWatcher to do just that.

import java.text.DecimalFormat;
import java.text.ParseException;

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

public class NumberTextWatcher implements TextWatcher {
    
    private DecimalFormat df;
    private DecimalFormat dfnd;
    private boolean hasFractionalPart;
    
    private EditText et;
    
    public NumberTextWatcher(EditText et)
    {
        df = new DecimalFormat("#,###.##");
        df.setDecimalSeparatorAlwaysShown(true);
        dfnd = new DecimalFormat("#,###");
        this.et = et;
        hasFractionalPart = false;
    }
    
    @SuppressWarnings("unused")
    private static final String TAG = "NumberTextWatcher";

    @Override
    public void afterTextChanged(Editable s)
    {
        et.removeTextChangedListener(this);
        
        try {
            int inilen, endlen;
            inilen = et.getText().length();
            
            String v = s.toString().replace(String.valueOf(df.getDecimalFormatSymbols().getGroupingSeparator()), "");
            Number n = df.parse(v);
            int cp = et.getSelectionStart();
            if (hasFractionalPart) {
                et.setText(df.format(n));
            } else {
                et.setText(dfnd.format(n));
            }
            endlen = et.getText().length();
            int sel = (cp + (endlen - inilen));
            if (sel > 0 && sel <= et.getText().length()) {
                et.setSelection(sel);
            } else {
                // place cursor at the end?
                et.setSelection(et.getText().length() - 1);
            }
        } catch (NumberFormatException nfe) {
            // do nothing?
        } catch (ParseException e) {
            // do nothing?
        }
        
        et.addTextChangedListener(this);
    }

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

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

}

To use it, all you need to do is add a TextChangedListener to the EditText component.

editText.addTextChangedListener(new NumberTextWatcher(editText));


You can get the source code directly from GitHub.



16 comments:

  1. This works perfectly but it does not allow leading zeros after the decimal point
    e.g I can not type 1.000000003
    no zeros can go on after the decimal point
    please fix this

    ReplyDelete
    Replies
    1. Thank you so much Roshka but Paul is right , let me to fix this bug !
      in onTextChanged():
      int index = s.toString().indexOf(String.valueOf(df.getDecimalFormatSymbols().getDecimalSeparator()));
      trailingZeroCount = 0;
      if (index > -1)
      {
      for (index++; index < s.length(); index++) {
      if (s.charAt(index) == '0')
      trailingZeroCount++;
      else {
      trailingZeroCount = 0;
      }
      }

      hasFractionalPart = true;
      } else {
      hasFractionalPart = false;
      }

      then in afterTextChanged():

      if (hasFractionalPart) {
      StringBuilder trailingZeros = new StringBuilder();
      while (trailingZeroCount-- > 0)
      trailingZeros.append('0');
      amount.setText(df.format(n) + trailingZeros.toString());
      } else {
      amount.setText(dfnd.format(n));
      }

      Delete
    2. Thanks for your fix, and thank you Roshka.
      All of the best to you bunch of good people.

      Delete
    3. Hi 109862803127285470647.
      I'm sorry but it doesn't accept 0 (zero) after comma (symbol ,).

      My locale definitions are these:
      private final Locale locale = new Locale("pt", "BR");
      df = new DecimalFormat("##,###,###.##", new DecimalFormatSymbols(locale));
      dfnd = new DecimalFormat("#,###,###", new DecimalFormatSymbols(locale));

      Any help?
      Regards.

      Delete
  2. Wooooooooow! Pretty cool! this saved my ass. Thanks!

    ReplyDelete
  3. This is really good just have a problem that is appearing voids and would appear to be #,###.## instead of # ### ##. how can I do this? Thank you very much

    ReplyDelete
  4. Thank you very much...this saved my day

    ReplyDelete
  5. Thank you very much, i'm looking for it.Good man !

    ReplyDelete
  6. Hello, I served very good but if instead you pull me #, ## I want to pull me q #. ##.
    Change "," by "." ??? try changing it in the String but pretended not to recognize me.
    As you can do is ????

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hi there.
      You gotta use locale. In my case, I have:
      private final Locale locale = new Locale("pt", "BR");
      df = new DecimalFormat("##,###,###.##", new DecimalFormatSymbols(locale));
      dfnd = new DecimalFormat("#,###,###", new DecimalFormatSymbols(locale));
      Then it will work...

      But I have a question below, for it doesn't accept 0 (zero) after comma (symbol ,).
      The correction proposed by 109862803127285470647 didn't work for me.

      Regards.

      Delete
  7. Como puedo haacer para que sea un punto en vez de una coma???

    Ejemplo: 1.85

    Trate cambiando el String pero no resulta, tambien probe poniendo un float pero no me salio tampoco.

    ReplyDelete

  8. Muchas gracias por la ayuda !!

    ReplyDelete