EditText格式化输入内容

在Android开发中,格式化输入框输入的手机号码是一个很常用的事情,比如输入13333333333,最终需要格式化成为133 3333 3333格式。这是一个很常见的需求,但是实现起来还是有点麻烦,主要体现在以下几个地方:

  1. 在中间插入或者删除
  2. 复制粘贴的处理
  3. 光标的处理。很多app都是格式化完成后直接将光标定位到最后,或者直接强制关闭中间位置修改功能,或者关闭复制粘贴功能。

这里先介绍一下一个很重要的接口,TextWatcher
TextWatcher里面有三个方法:

beforeTextChanged(CharSequence s, int start, int count, int after)
onTextChanged(CharSequence s, int start, int before, int count)
afterTextChanged(Editable s)

这里有几个比较抽象的参数:beforeTextChanged的几个参数的意义分别是:

  • CharSequence s: 变动之前的输入框中的内容
  • int start: 发生变动的起始位置
  • int count: 发生变动的时候影响到原字符串的个数(在插入的时候一般是0,删除的时候一般不为0)
  • int after: 和上面的count相关,意思是上面count个字符串变成了after个字符串

这个会比较抽象,我们可以在代码中打印一下:

	et.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            Log.i("edittext", "beforeTextChanged   CharSequence:(" + s + ");  start: (" + start + "); count: (" + count + "); after: (" + after + ")");
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            Log.i("edittext", "onTextChanged   CharSequence:(" + s + ");  start: (" + start + "); before: (" + before + "); count: (" + count + ")");
        }

        @Override
        public void afterTextChanged(Editable s) {
            Log.i("edittext", "分割线---------------------------------------------------------------------------------------------\n");
        }
    });

EditText格式化输入内容
我们看下打印下来的日志:
EditText格式化输入内容
我们在第七个字符的位置输入了3个m,所以应该是有三个流程打印出来的。

  • 第一个日志 start = 6, count = 0, after = 1;我们输入的位置是第七个字符的位置,所以下标应该是6,在下标为6的地方往后0个字符被修改为了1个字符。我们是输入了一个字符,所以没有字符被修改,count = 0没毛病,然后新增了一个字符m,所以after = 1,也没毛病。
  • 第二三个日志也是一样的,由于输入了字符,所以start的位置因该向前加一位,其他都一样。

我们可以再看下批量删除的日志,验证一下我们的结论:
EditText格式化输入内容
日志:
EditText格式化输入内容

我们删除了中间的三个mmm,看下日志,start = 6, count = 3, after = 0;从第六个字符开始后的3个字符被替换成了0个字符,所以是删除了三个字符,验证了我们的结论。
大家有兴趣可以自己再去验证一下粘贴复制这种情况,这里不再做论证。

简单介绍了下这几个参数的意义,下面我们分析一下上面说到的需求,格式化手机号:

当我们输入手机号的时候需要自动添加空格,而当我们删除手机号的时候也需要自动删除空格,我们可以根据beforeTextChanged()的参数方法判断出来当前是出于新增还是删除的情况:

public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    if (s.length() == count && start == 0 && count != 0) {
        inputMode = MODE_SET_TEXT;
    } else if (after == 0) {
        inputMode = MODE_DELETE;
        curIndex = start;
        if ((curIndex == 4 && s.length() >= 4) || (curIndex == 9 && s.length() >= 9)){
            curIndex -= 1;
        }
    } else {
        inputMode = MODE_INSERT;
        curIndex = after + start;
        if ((curIndex == 4 && s.length() >= 4) || (curIndex == 9 && s.length() >= 9)){
            curIndex += 1;
        }
    }
}

这里需要注意,除了新增和删除,还有可能调用到setText()这个方法,当我们手动修改完text的时候需要通过setText()设置进去,这个时候需要一些特殊处理(当然也可以通过修改Editable参数修改)。curIndex参数用来记录当前光标所在的位置,这样可以使用户进行输入和删除包括复制粘贴的时候让光标停留在自己想要的位置,毕竟提高用户体验是我们的必要操作。
具体的代码贴在下面,大家感兴趣可以看看:

public class PhoneNumberTextWatcher implements TextWatcher {

    private static final int MODE_INSERT = 0;
    private static final int MODE_DELETE = 1;
    private static final int MODE_SET_TEXT = 2;

    /**
     * 当前的输入模式,默认
     */
    private int inputMode;
    /**
     * 当前的光标所在位置
     */
    private int curIndex;
    /**
     * 输入手机号码的editText
     */
    private EditText etPhone;

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (s.length() == count && start == 0 && count != 0) {
            inputMode = MODE_SET_TEXT;
        } else if (after == 0) {
            inputMode = MODE_DELETE;
            curIndex = start;
            if ((curIndex == 4 && s.length() >= 4) || (curIndex == 9 && s.length() >= 9)){
                curIndex -= 1;
            }
        } else {
            inputMode = MODE_INSERT;
            curIndex = after + start;
            if ((curIndex == 4 && s.length() >= 4) || (curIndex == 9 && s.length() >= 9)){
                curIndex += 1;
            }
        }
    }

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

    }

    @Override
    public void afterTextChanged(Editable s) {
        String curString = s.toString();
        String realString = curString.replace(" ", "");
        int length = realString.length();
        StringBuilder sb = new StringBuilder();
        if (inputMode == MODE_INSERT) {
            if (length < 3) {
                return;
            }
            sb.append(realString.substring(0, 3));
            sb.append(" ");
            if (length < 7) {
                sb.append(realString.substring(3, length));
            } else {
                sb.append(realString.substring(3, 7));
                sb.append(" ");
                sb.append(realString.substring(7, length));
            }
        } else if (inputMode == MODE_DELETE) {
            if (length <= 3) {
                sb.append(realString);
            } else if (length <= 7) {
                sb.append(realString.substring(0, 3));
                sb.append(" ");
                sb.append(realString.substring(3, length));
            } else {
                sb.append(realString.substring(0, 3));
                sb.append(" ");
                sb.append(realString.substring(3, 7));
                sb.append(" ");
                sb.append(realString.substring(7, length));
            }
        }
        if (sb.length() != 0 && !sb.toString().equals(curString)) {
            if (etPhone != null) {
                etPhone.setText(sb);
            }
        }
        if (length != 0 && etPhone != null) {
            etPhone.setSelection(curIndex);
        }
    }

    public void setEditText(EditText etPhone){
        this.etPhone = etPhone;
        this.etPhone.addTextChangedListener(this);
    }
} 

我们看下最终的效果:
EditText格式化输入内容
使用方式:

	EditText et = findViewById(R.id.et);
    new PhoneNumberTextWatcher().setEditText(et);

对于银行卡号或者身份证的格式化同理也是这种思路。