26

how to make edit text accept input in format

4digitnumber-4dignumber-4dignumber-4dignumber   

The code

text.addTextChangedListener(new TextWatcher() {
    int len = 0;
    String string ;
    @Override

    public void afterTextChanged(Editable s) {

        text.setOnKeyListener(new OnKeyListener()
        {   public boolean onKey(View v, int keyCode, KeyEvent event)
            {              
                    if (keyCode == KeyEvent.KEYCODE_DEL)
                    {

                    }
                    else{

                        string = text.getText().toString();
                        len = string.length()+1;
                        if(len%5==0){text.append("-");}

             }

                return false;      }   });
    }
});

works fine upon adding, but deleting or editing causes problem.

Rahul
  • 1,169
  • 1
  • 12
  • 27

8 Answers8

26

Now this works fine for soft/hard keyboard for all delete/edit ops. tx 4 ur help..

package com.and;

import android.app.Activity;
import android.app.AlertDialog;
import android.inputmethodservice.KeyboardView;
import android.os.Bundle;
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
import android.text.TextWatcher;
import android.text.format.Formatter;
import android.text.method.NumberKeyListener;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.EditText;
import android.widget.Toast;

public class ccformat extends Activity {

    String a;
    int keyDel;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final EditText text = (EditText) findViewById(com.and.R.id.editText1);

        text.addTextChangedListener(new TextWatcher() {

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

                boolean flag = true;
                String eachBlock[] = text.getText().toString().split("-");
                for (int i = 0; i < eachBlock.length; i++) {
                    if (eachBlock[i].length() > 4) {
                        flag = false;
                    }
                }
                if (flag) {

                    text.setOnKeyListener(new OnKeyListener() {

                        @Override
                        public boolean onKey(View v, int keyCode, KeyEvent event) {

                            if (keyCode == KeyEvent.KEYCODE_DEL)
                                keyDel = 1;
                            return false;
                        }
                    });

                    if (keyDel == 0) {

                        if (((text.getText().length() + 1) % 5) == 0) {

                            if (text.getText().toString().split("-").length <= 3) {
                                text.setText(text.getText() + "-");
                                text.setSelection(text.getText().length());
                            }
                        }
                        a = text.getText().toString();
                    } else {
                        a = text.getText().toString();
                        keyDel = 0;
                    }

                } else {
                    text.setText(a);
                }

            }

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

            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });
    }
}
Rahul
  • 1,169
  • 1
  • 12
  • 27
  • 2
    But for my code Code is crashing when i press back key...also spaces are not getting deleted for the above code. – Taruni Mar 28 '14 at 14:33
21

This is working:

public class EditTextSample extends Activity {
    // This regexp has to be improved, it does not detect case where you have
    // more than 4 digits in a middle group like: 1234-12345-123
    static final Pattern CODE_PATTERN = Pattern.compile("([0-9]{0,4})|([0-9]{4}-)+|([0-9]{4}-[0-9]{0,4})+");

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.edit_text_sample);

        final EditText editText = (EditText) findViewById(R.id.input);
        editText.addTextChangedListener(new TextWatcher() {

            @Override
            public void afterTextChanged(Editable s) {
                Log.w("", "input" + s.toString());

                if (s.length() > 0 && !CODE_PATTERN.matcher(s).matches()) {
                    String input = s.toString();
                    String numbersOnly = keepNumbersOnly(input);
                    String code = formatNumbersAsCode(numbersOnly);

                    Log.w("", "numbersOnly" + numbersOnly);
                    Log.w("", "code" + code);

                    editText.removeTextChangedListener(this);
                    editText.setText(code);
                    // You could also remember the previous position of the cursor
                    editText.setSelection(code.length());
                    editText.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) {
            }

            private String keepNumbersOnly(CharSequence s) {
                return s.toString().replaceAll("[^0-9]", ""); // Should of course be more robust
            }

            private String formatNumbersAsCode(CharSequence s) {
                int groupDigits = 0;
                String tmp = "";
                for (int i = 0; i < s.length(); ++i) {
                    tmp += s.charAt(i);
                    ++groupDigits;
                    if (groupDigits == 4) {
                        tmp += "-";
                        groupDigits = 0;
                    }
                }
                return tmp;
            }
        });
    }
}
Vincent Mimoun-Prat
  • 28,208
  • 16
  • 81
  • 124
  • Nice solution. Although a lower overhead method of locking out the watcher while making changes is simply to provide an editing boolean. This is how Google does is in their TextWatcher implementations. Still, +1. – devunwired May 10 '11 at 14:44
  • 1
    Indeed, much better if following Google's method, I did not think about it :/ (see there: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.1_r2/android/telephony/PhoneNumberFormattingTextWatcher.java) – Vincent Mimoun-Prat May 10 '11 at 15:03
  • tx..is there any way to set cursor at particular position,end of the text to be specific in Edit Text widget,after each edit.? – Rahul May 11 '11 at 06:00
  • editText.setSelection(code.length()); does it. But you could simply remember the position before setting the text using editText.getSelectionStart() – Vincent Mimoun-Prat May 11 '11 at 08:14
  • this solution results into kind of recursion and stack overflow – Cynichniy Bandera Apr 20 '13 at 12:06
  • @Umka you'll get recursion when you don't remove the listener before calling setText() within the listener. Or like mentioned before use a flag. – hcpl May 30 '13 at 14:29
  • Nice answer. Quite simple and pretty. It helps to me. – Luna Kong May 22 '18 at 09:32
7

If you want to just group visually the numbers, but you don't want to alter the value of the EditText adding dashes, you can use this Span approach:

EditText editText = findViewById(R.id.editText);
editText.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) {}

    @Override
    public void afterTextChanged(Editable editable) {
        Object[] paddingSpans = editable.getSpans(0, editable.length(), DashSpan.class);
        for (Object span : paddingSpans) {
            editable.removeSpan(span);
        }

        addSpans(editable);
    }

    private static final int GROUP_SIZE = 4;

    private void addSpans(Editable editable) {

        final int length = editable.length();
        for (int i = 1; i * (GROUP_SIZE) < length; i++) {
            int index = i * GROUP_SIZE;
            editable.setSpan(new DashSpan(), index - 1, index,
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }
});

where the DashSpan class looks like this:

/**
 * A {@link ReplacementSpan} used for spacing in {@link android.widget.EditText}
 * to space things out. Adds '-'s
 */
public class DashSpan extends ReplacementSpan {

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
        float padding = paint.measureText("-", 0, 1);
        float textSize = paint.measureText(text, start, end);
        return (int) (padding + textSize);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
                     int bottom, @NonNull Paint paint) {
        canvas.drawText(text.subSequence(start, end) + "-", x, y, paint);
    }
}

This way you will have visually the grouping using the dashes, but the getText() will return the text without any grouping.

To force only numbers you can add the attributes android:digits="0123456789" and android:inputType="number" to the EditText.

This solution is based on the code of this library.

Roberto Leinardi
  • 10,641
  • 6
  • 65
  • 69
3

In my case below code is working fine.

editTextCreditCard.addTextChangedListener(new FourDigitCardFormatWatcher());

Add custom class for TextWatcher.

public class FourDigitCardFormatWatcher implements TextWatcher {

        private static final char space = ' ';

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

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

        @Override
        public void afterTextChanged(Editable s) {
            if (s.length() > 0 && (s.length() % 5) == 0) {
                final char c = s.charAt(s.length() - 1);
                if (space == c) {
                    s.delete(s.length() - 1, s.length());
                }
            }
            if (s.length() > 0 && (s.length() % 5) == 0) {
                char c = s.charAt(s.length() - 1);
                if (Character.isDigit(c) && TextUtils.split(s.toString(), String.valueOf(space)).length <= 3) {
                    s.insert(s.length() - 1, String.valueOf(space));
                }
            }
        }
    }

Hope this would help you.

Hiren Patel
  • 52,124
  • 21
  • 173
  • 151
1

It works in all cases, when you insert or remove a character, the format will always be right. Make sure you set

android:inputType="number"

/

myEditText.addTextChangedListener(new TextWatcher() {
        private final String space = "-"; // you can change this to whatever you want
        private final Pattern pattern = Pattern.compile("^(\\d{4}"+space+"{1}){0,3}\\d{1,4}$"); // check whether we need to modify or not
        @Override
        public void onTextChanged(CharSequence s, int st, int be, int count) {
            String currentText = myEditText.getText().toString();
            if (currentText.isEmpty() || pattern.matcher(currentText).matches())
                return; // no need to modify
            String numbersOnly = currentText.trim().replaceAll("[^\\d.]", "");; // remove everything but numbers
            String formatted = "";
            for(int i = 0; i < numbersOnly.length(); i += 4)
                if (i + 4 < numbersOnly.length())
                    formatted += numbersOnly.substring(i,i+4)+space;
                else
                    formatted += numbersOnly.substring(i);
            myEditText.setText(formatted);
            myEditText.setSelection(myEditText.getText().toString().length());
        }
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void afterTextChanged(Editable e) {}
});
AndroidDev
  • 84
  • 10
0

It seems to me the answers presented here do not work properly with delete, delete from the middle operations, etc. Here is my code. It doesn't restrict the length of input, but seems to be ok with various insertions and deletions:

import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;

public class HyphenDelimitTextWatcher implements TextWatcher {
    EditText mEditText;
    boolean mInside = false;
    boolean mWannaDeleteHyphen = false;
    boolean mKeyListenerSet = false;
    final static String MARKER = "|"; // filtered in layout not to be in the string

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if(!mKeyListenerSet) {
            mEditText.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    try {
                        mWannaDeleteHyphen = (keyCode == KeyEvent.KEYCODE_DEL
                                && mEditText.getSelectionEnd() - mEditText.getSelectionStart() <= 1
                                && mEditText.getSelectionStart() > 0
                                && mEditText.getText().toString().charAt(mEditText.getSelectionEnd() - 1) == '-');
                    } catch (IndexOutOfBoundsException e) {
                        // never to happen because of checks
                    }
                    return false;
                }
            });
            mKeyListenerSet = true;
        }
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (mInside) // to avoid recursive calls
            return;
        mInside = true;

        int currentPos = mEditText.getSelectionStart();
        String string = mEditText.getText().toString().toUpperCase();
        String newString = makePrettyString(string);

        mEditText.setText(newString);
        try {
            mEditText.setSelection(getCursorPos(string, newString, currentPos, mWannaDeleteHyphen));
        } catch (IndexOutOfBoundsException e) {
            mEditText.setSelection(mEditText.length()); // last resort never to happen
        }

        mWannaDeleteHyphen = false;
        mInside = false;
    }

    @Override
    public void afterTextChanged(Editable s) {
    }

    private String makePrettyString(String string) {
        String number = string.replaceAll("-", "");
        boolean isEndHyphen = string.endsWith("-") && (number.length()%4 == 0);
        return number.replaceAll("(.{4}(?!$))", "$1-") + (isEndHyphen ?"-":"");
    }

    private int getCursorPos(String oldString, String newString, int oldPos, boolean isDeleteHyphen) {
        int cursorPos = newString.length();
        if(oldPos != oldString.length()) {
            String stringWithMarker = oldString.substring(0, oldPos) + MARKER + oldString.substring(oldPos);

            cursorPos = (makePrettyString(stringWithMarker)).indexOf(MARKER);
            if(isDeleteHyphen)
                cursorPos -= 1;
        }
        return cursorPos;
    }

    public HyphenDelimitTextWatcher(EditText editText) {
        mEditText = editText;
    }
}

Usage:

    mSomeEditText.addTextChangedListener(new HyphenDelimitTextWatcher(mSomeEditText));
Mirron
  • 73
  • 7
0

if you neeed this efect,ou can use this code in EditText

enter image description here

Community
  • 1
  • 1
David Hackro
  • 3,652
  • 6
  • 41
  • 61
0

Here is a formatting regex used to show card details in format XXXX XXXX XXXX XXXX

etCreditCardNumber.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) {
                etCreditCardNumber.setFloatingLabel(MaterialEditText.FLOATING_LABEL_HIGHLIGHT);

                String initial = s.toString();
                // remove all non-digits characters
                String processed = initial.replaceAll("\\D", "");

                // insert a space after all groups of 4 digits that are followed by another digit
                processed = processed.replaceAll("(\\d{4})(?=\\d)(?=\\d)(?=\\d)", "$1 ");

                //Remove the listener
                etCreditCardNumber.removeTextChangedListener(this);

                int index = etCreditCardNumber.getSelectionEnd();

                if (index == 5 || index == 10 || index == 15)
                    if (count > before)
                        index++;
                    else
                        index--;

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

                try {
                    etCreditCardNumber.setSelection(index);
                } catch (Exception e) {
                    e.printStackTrace();
                    etCreditCardNumber.setSelection(s.length() - 1);
                }
                //Give back the listener
                etCreditCardNumber.addTextChangedListener(this);
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
Harpreet
  • 2,990
  • 3
  • 38
  • 52