背景

为了优化用户体验,我们往往会在让用户输入手机号码时添加空格,比如:133 1234 5678.那么在Android中如何实现呢?

实现方法

Android中的输入框EditText有个方法addTextChangedListener(),该方法实现输入框文字变动时的监听.该方法需要传入一个TextWatcher接口的实现.

TextWatcher接口如下:

public interface TextWatcher extends NoCopySpan {

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

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

    public void afterTextChanged(Editable s);
}

该接口有三个回调函数,其含义如下:

public abstract void beforeTextChanged (CharSequence s, int start, int count, int after):

This method is called to notify you that, within s, the count characters beginning at start are about to be replaced by new text with length after. It is an error to attempt to make changes to s from this callback.
在字符串s内,从索引为start(包含)的字符开始的count个字符将被长度为after的新文本代替

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

This method is called to notify you that, within s, the count characters beginning at start have just replaced old text that had length before. It is an error to attempt to make changes to s from this callback.
在字符串s内,从索引为start(包含)的字符开始count个字符刚刚替换了长度为before的旧字符.;

public abstract void afterTextChanged (Editable s)

This method is called to notify you that, somewhere within s, the text has been changed. It is legitimate to make further changes to s from this callback, but be careful not to get yourself into an infinite loop, because any changes you make will cause this method to be called again recursively. (You are not told where the change took place because other afterTextChanged() methods may already have made other changes and invalidated the offsets. But if you need to know here, you can use setSpan(Object, int, int, int) in onTextChanged(CharSequence, int, int, int) to mark your place and then look up from here where the span ended up.
字符串s内容已经发生了变化.可以在这一步对s进行合理的变更,但是要注意不要进入无限循环,因为字符串的任何变化都会再次递归调用此回调方法.在这个方法中不会告诉你字符串哪些内容发生了变化,因为其他针对字符串的改变已经调用了afterTextChanged().如果你想知道哪些发生了变化,可以在onTextChanged(CharSequence, int, int, int)使用setSpan(Object, int, int, int)做标记.

看上面的解释,是不是还是有点糊涂呢?下面就用实例来解释一下吧.

实例代码

代码很简单,只有一个输入框EditText,然后添加一个TextWatcher实现,将回调中的结果输出.

public class MainActivity extends AppCompatActivity {

    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = (EditText) findViewById(R.id.edit_text);
        editText.addTextChangedListener(textWatcher);
    }

    private final TextWatcher textWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            DLog.d("----------beforeTextChanged----------\n");
            DLog.d("s:" + s + "\n");
            DLog.d("start:" + start + "\n");
            DLog.d("count:" + count + "\n");
            DLog.d("after:" + after + "\n");
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            DLog.d("----------onTextChanged----------\n");
            DLog.d("s:" + s + "\n");
            DLog.d("start:" + start + "\n");
            DLog.d("before:" + before + "\n");
            DLog.d("count:" + count + "\n");
        }

        @Override
        public void afterTextChanged(Editable s) {
            DLog.d("----------afterTextChanged----------\n");
            DLog.d("s:" + s + "\n");
        }
    };
}

其中Dlog类参见此处:Android调试Log二次包装

输出结果

默认输入框没有任何内容,以下操作在上一步之后进行.

输入一个字符

在输入框输入字符:1,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:
start:0
count:0
after:1
//注释:在字符串""从索引0处开始的0个字符将被长度为1的新字符串代替
//即""将被1代替
----------onTextChanged----------
s:1
start:0
before:0
count:1
//注释:字符串"1"从索引0处开始的1个字符刚刚替换了长度为0的旧字符串
//即1刚刚代替了""
----------afterTextChanged----------
s:1
//注释:发生变化后的字符串为:"1"

输入两个字符

在输入框一次性输入字符(即粘贴):23,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:1
start:1
count:0
after:2
//注释:在字符串"1"从索引1开始的0个字符将被长度为2的新字符串代替
//即""将被23代替
----------onTextChanged----------
s:123
start:1
before:0
count:2
//注释:字符串"123"从索引1开始的2个字符刚刚替换了长度为0的旧字符串
//即23刚刚代替了""
----------afterTextChanged----------
s:123
//注释:发生变化后的字符串为:"123"

输入三个字符

在输入框一次性输入字符(即粘贴):456,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:123
start:3
count:0
after:3
//注释:在字符串"123"从索引3开始的0个字符将被长度为3的新字符串代替
//即""将被456代替
----------onTextChanged----------
s:123456
start:3
before:0
count:3
//注释:字符串"123456"从索引3开始的3个字符刚刚替换了长度为0的旧字符串
//即456刚刚代替了""
----------afterTextChanged----------
s:123456
//注释:发生变化后的字符串为:"123456"

删除一个字符

在输入框删除字符:6,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:123456
start:5
count:1
after:0
//注释:在字符串"123456"从索引5开始的1个字符将被长度为0的新字符串代替
//即6将被""代替
----------onTextChanged----------
s:12345
start:5
before:1
count:0
//注释:字符串"12345"从索引5开始的0个字符刚刚替换了长度为1的旧字符串
//即""刚刚代替了6
----------afterTextChanged----------
s:12345
//注释:发生变化后的字符串为:"12345"

删除两个字符

在输入框一次性删除字符:45,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:12345
start:3
count:2
after:0
//注释:在字符串"12345"从索引3开始的2个字符将被长度为0的新字符串代替
//即45将被""代替
----------onTextChanged----------
s:123
start:3
before:2
count:0
//注释:字符串"123"从索引3开始的0个字符刚刚替换了长度为2的旧字符串
//即""刚刚代替了45
----------afterTextChanged----------
s:123
//注释:发生变化后的字符串为:"123"

将一个字符替换为两个字符

在输入框选中3粘贴为ab,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:123
start:2
count:1
after:2
//注释:在字符串"123"从索引2开始的1个字符将被长度为2的新字符串代替
//即3将被ab代替
----------onTextChanged----------
s:12ab
start:2
before:1
count:2
//注释:字符串"12ab"从索引2开始的2个字符刚刚替换了长度为1的旧字符串
//即ab刚刚代替了3
----------afterTextChanged----------
s:12ab
//注释:发生变化后的字符串为:"12ab"

将两个字符替换为一个字符

在输入框选中ab粘贴为3,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:12ab
start:2
count:2
after:1
//注释:在字符串"12ab"从索引2开始的2个字符将被长度为1的新字符串代替
//即ab将被3代替
----------onTextChanged----------
s:123
start:2
before:2
count:1
//注释:字符串"123"从索引2开始的1个字符刚刚替换了长度为2的旧字符串
//即3刚刚代替了ab
----------afterTextChanged----------
s:123
//注释:发生变化后的字符串为:"123"

任意位置插入一个字符串

在输入框中2的后面输入5,各回调方法输出的信息如下(注释为作者后来添加):

----------beforeTextChanged----------
s:123
start:2
count:0
after:1
//注释:在字符串"123"从索引2开始的0个字符将被长度为1的新字符串代替
//即""将被5代替
----------onTextChanged----------
s:1253
start:2
before:0
count:1
//注释:字符串"1253"从索引2开始的1个字符刚刚替换了长度为0的旧字符串
//5刚刚代替了""
----------afterTextChanged----------
s:1253
//注释:发生变化后的字符串为:"1253"

解决需求

从上面我们可以看出,在onTextChanged方法中字符串已经发生变化,我们可以在此对用户输入的字符串追加空格.
用户是一个数字一个数字的输入到输入框中,那么条件为:

字符串s从索引start开始的1个字符刚刚替换了""

public void onTextChanged(CharSequence s, int start, int before, int count) {
    DLog.d("----------onTextChanged----------\n");
    DLog.d("s:" + s + "\n");
    DLog.d("start:" + start + "\n");
    DLog.d("before:" + before + "\n");
    DLog.d("count:" + count + "\n");

    if (count == 1){
        int length = s.toString().length();
        if (length == 3 || length == 8){
            editText.setText(s + " ");
            editText.setSelection(editText.getText().toString().length());
        }
    }
}

当长度为3和8时分别加上空格.

验证需求

运行程序得到结果如下:


重要说明

想随时获取最新博客文章更新,请关注公共账号DevWiki,或扫描下面的二维码:

微信公共号