从 Formatter 学习 TextWatcher

电话号码和银行号码经常会有需要格式话为固定的样式,比如银行卡号通常会格式化为 XXXX XXXX XXXX XXXX,手机号通常会格式化为 XXX XXXX XXXX,对于手机号码可以用 Android 自带的 PhoneNumberFormattingTextWatcher, 但是这种格式化的方式有时候并不是我们想要的,那就需要自己来实现一个 TextWatcher 了。

第一次尝试实现

先尝试实现一次,在每次text有更新的时候做一次 format, 代码如下(省略 import 的内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class FormatterTest extends Activity {

private final char SEPARATOR = ' ';

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.formatter_test);
EditText editText = (EditText) findViewById(R.id.phone_num);
editText.addTextChangedListener(new TextWatcher() {

private boolean mStopFormatting;

@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 s) {
if(mStopFormatting) {
return;
}
mStopFormatting = true;
format(s);
mStopFormatting = false;
}
});
}

private String format(String s) {
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
format(stringBuilder);
return stringBuilder.toString();
}

private void format(Editable s) {
clean(s);
if (s.length() < 4) {
return;
}
int i = 0;
int valid = 0;
while (i < s.length()) {
if (valid % 4 == 0 && valid != 0) {
s.insert(i, Character.toString(SEPARATOR));
i++;
}
i++;
valid++;
}
}

private void clean(Editable s) {
int p = 0;
while (p < s.length()) {
if (!Character.isDigit(s.charAt(p))) {
s.delete(p,p+1);
}
p++;
}
}
}

运行之后可以正常的format,不过将光标移动到空格位置删除的时候却无法删除。
原因就是实际上是删除成功了,但是在 afterTextChanged 的时候又格式化了,因此空格在删除后又会出现,给人的感觉就是无法删除。


Handler和Timer作为计时器的优劣

Android中可以采用Timer+Handler或者单纯使用Handler的方法来实现定时通知的功能,但是采用Handler的方法会更好,优点不仅仅在于代码量会少很多,更主要在使用于Timer会产生bug。

使用Timer+Handler的方式

这种方式在TimerTask的run方法中sendMessage给Handler,实现定时操作的目的,但是使用Timer本身会出现一种问题,看下面的实验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
print("first");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
print("second");
}
};

Timer timer = new Timer();
timer.schedule(timerTask, 0, 1000);

愿意是想立即执行一次这样的任务:先输出first,隔2秒再输出second,然后再隔1秒,再执行一次同样的任务,但是当执行过后就会发现,second输出完之后会马上输出first,并没有延迟1秒,问题就在于timer的时间延迟是从任务开始执行时就已经计算了,并不是上一次任务执行完成后才计算的,因此这个任务执行了2秒,其实第二次任务已经开始执行了,但是是单队列,所以并没有马上出现,但是下一次已经不会再如我们所愿的延迟1秒了(但是它其实也按照它自己的方式延迟了1秒,也就是它不在乎任务有没有执行完,我自己发出命令就开始计算1秒,然后再发出命令),因此这种方式可能会出现问题。

只使用Handler的方式

这种方式既简单也没有风险:

1
handler.sendEmptyMessageDelayed(MSG_TIME_COUNT, 1000);

一句代码即可搞定,会延迟1秒发送消息给handleMessage来处理,而且每次是等任务执行完才会开始延迟。