Edit-InputFilter

InputFilter 会在 TextWatcher 之前调用,可以在用户输入内容时就禁止用户输入一些非法字符。

1
2
3
4
5
6
7
public interface InputFilter{
/**
* 即将使用 source[start, end - 1] 替换 dest[dstart, dend - 1]。
* 通过返回值可以修改执行替换的部分,如果返回值是 null,则不进行任何干涉。
*/
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend);
}

官网的注释特别提醒了一句:

If <var>source</var> is an instance of Spanned or Spannable, 
the span objects in the <var>source</var> should be copied into the filtered result (i.e. the non-null return value). 
TextUtils#copySpansFrom can be used for convenience if the span boundary indices would be remaining identical relative to the source.

如果 source 是 Spanned 或者 Spannable 的实例,如果需要进行替换,则在返回值的时候,应该复制 source 的内容,然后再复制的 source 上进行操作,最后返回被复制的 source。
这么处理的考虑是因为 source 本身可能存在一些样式,如果直接返回 CharSequence,会丢失原有的 span 样式。

LengthFilter

InputFilter 有一个自带的长度过滤器:LengthFilter。EditText 的 android:maxLength=”3” 属性就是通过 LengthFilter 产生效果的。

不过 LengthFilter 有一个令人误解的地方: LengthFilter 限制的是字符长度,而不是文字的长度。比如对于 LengthFilter 来说,“陶吉吉” 与 “陶𠮷” 的字符长度是一样的。

这其中涉及的是 UTF-16 的问题,因为 “𠮷” 的 Unicode 码点为 0x20BB7,UTF-16 编码为 0xD842 0xDFB7 ,字符长度是 2 。

LengthFilter 的实际处理:

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

public static class LengthFilter implements InputFilter {

//...

public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
int keep = mMax - (dest.length() - (dend - dstart));
if (keep <= 0) {
return "";
} else if (keep >= end - start) {
return null; // keep original
} else {
keep += start;
if (Character.isHighSurrogate(source.charAt(keep - 1))) {
--keep;
if (keep == start) {
return "";
}
}
return source.subSequence(start, keep);
}
}

//...

其中

1
2
3
4
5
6
7
8

if (Character.isHighSurrogate(source.charAt(keep - 1))) {
--keep;
if (keep == start) {
return "";
}
}

是产生效果的部分,如果最后一个字符是某个文字的高位字符,直接就抛弃了。我们输入 “陶大𠮷” 的时候,source 的内容是

\u9676\u5927\ud842\udfb7

参数值:

CharSequence source, // 陶大𠮷
int start, // 0
int end, // 4
Spanned dest, // ""
int dstart, // 0
int dend // 0

判断出 \ud842 属于 \ud842\udfb7 的高位,因此 \ud842\udfb7-“𠮷” 被整个丢弃了。

除了这些生僻汉字以外,emoji 表情也是占用两个字符。于是在一个 android:maxLength=”3” 的输入框内,最多只能容纳一个 emoji 表情。这种限制可能不符合我们实际的需求,所以就需要自定义 InputFilter 来处理了。

参考

  1. 彻底弄懂 Unicode 编码