Search code examples
androidandroid-edittextandroid-4.0-ice-cream-sandwich

How do I implement specific phone number formatting with an EditText and ICS?


My app requires that phone numbers be formatted like xxx-xxx-xxxx. The code below worked prior to ICS, but ICS devices completely ignore the formatting text watcher. Why does it ignore the formatter and how can I get ICS to follow the expected format?

I'm setting up the filter and text listener as:

final EditText phoneNumber = (EditText) findViewById(R.id.phoneNumber);
phoneNumber.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
phoneNumber.setFilters(new InputFilter[] { new PhoneNumberFilter(), new InputFilter.LengthFilter(12) });

Code for the PhoneNumberFilter:

import android.text.InputType;
import android.text.Spanned;
import android.text.method.NumberKeyListener;

public class PhoneNumberFilter extends NumberKeyListener {

    @Override
    public int getInputType() {
        return InputType.TYPE_CLASS_PHONE;
    }

    @Override
    protected char[] getAcceptedChars() {
        return new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' };
    }

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

        // Don't let phone numbers start with 1
        if (dstart == 0 && source.equals("1")) 
            return "";

        return super.filter(source, start, end, dest, dstart, dend);
    }
}

Solution

  • I solved this with the following:

    final EditText phoneNumber = (EditText) findViewById(R.id.phoneNumber);
    phoneNumber.addTextChangedListener(new PhoneNumberTextWatcher());
    phoneNumber.setFilters(new InputFilter[] { new PhoneNumberFilter(), new InputFilter.LengthFilter(12) });
    

    PhoneNumberFilter - ensures that the phone number matches XXX-XXX-XXXX:

    public class PhoneNumberFilter extends NumberKeyListener {
    
        @Override
        public int getInputType() {
            return InputType.TYPE_CLASS_PHONE;
        }
    
        @Override
        protected char[] getAcceptedChars() {
            return new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' };
        }
    
        @Override
        public CharSequence filter(CharSequence source, int start, int end,
                Spanned dest, int dstart, int dend) {
    
            // Don't let phone numbers start with 1
            if (dstart == 0 && source.equals("1")) 
                return "";
    
            if (end > start) {
                String destTxt = dest.toString();
                String resultingTxt = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
    
                // Phone number must match xxx-xxx-xxxx
                if (!resultingTxt.matches ("^\\d{1,1}(\\d{1,1}(\\d{1,1}(\\-(\\d{1,1}(\\d{1,1}(\\d{1,1}(\\-(\\d{1,1}(\\d{1,1}(\\d{1,1}(\\d{1,1}?)?)?)?)?)?)?)?)?)?)?)?")) { 
                    return "";
                }
            }
            return null;
        }
    }
    

    PhoneNumberTextWatcher - handles auto inserting dashes and auto removing dashes if the user is deleting:

    public class PhoneNumberTextWatcher implements TextWatcher {
    
        private boolean isFormatting;
        private boolean deletingHyphen;
        private int hyphenStart;
        private boolean deletingBackward;
    
        @Override
        public void afterTextChanged(Editable text) {
            if (isFormatting)
                return;
    
            isFormatting = true;
    
            // If deleting hyphen, also delete character before or after it
            if (deletingHyphen && hyphenStart > 0) {
                if (deletingBackward) {
                    if (hyphenStart - 1 < text.length()) {
                        text.delete(hyphenStart - 1, hyphenStart);
                    }
                } else if (hyphenStart < text.length()) {
                    text.delete(hyphenStart, hyphenStart + 1);
                }
            }
            if (text.length() == 3 || text.length() == 7) {
                text.append('-');
            }
    
            isFormatting = false;
        }
    
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            if (isFormatting)
                return;
    
            // Make sure user is deleting one char, without a selection
            final int selStart = Selection.getSelectionStart(s);
            final int selEnd = Selection.getSelectionEnd(s);
            if (s.length() > 1 // Can delete another character
                    && count == 1 // Deleting only one character
                    && after == 0 // Deleting
                    && s.charAt(start) == '-' // a hyphen
                    && selStart == selEnd) { // no selection
                deletingHyphen = true;
                hyphenStart = start;
                // Check if the user is deleting forward or backward
                if (selStart == start + 1) {
                    deletingBackward = true;
                } else {
                    deletingBackward = false;
                }
            } else {
                deletingHyphen = false;
            }
        }
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }
    }