背景
需要实现EditText限制字数需求,直接在xml中设置android:maxLength属性就能解决;但是产品要求输入超出最大数的时候要Toast提示用户,并且还要阻止用户继续输入。**
初始方案是通过实现TextWatcher来处理,可以实现主要的需求逻辑,还是遇到了问题
- 监听和重新设置处理之后的文本时,会再次触发
TextWatcher的回调 - 处理输入文本替换选中文本的逻辑比较复杂
问题1还比较好处理,在onTextChanged方法中处理文本,处理之前先移除自身的TextWatcher对象,在处理完文本并重新设置到EditText上之后再重新添加;对问题2直接选择放弃。
分析
既然有maxLength的属性,那就看看源码是怎么实现的。
跟踪一下获取maxLength的属性的代码会发现,是通过设置InputFilter对象来实现的,具体实现就是InputFilter.LengthFilter,源码如下:
public static class LengthFilter implements InputFilter {
@UnsupportedAppUsage
private final int mMax;
public LengthFilter(int max) {
mMax = max;
}
/**
* @param source 输入的内容
* @param start 输入的内容的起始位置,即0
* @param end 输入的内容的结束位置,也就是source的长度
* @param dest 已存在的内容
* @param dstart 光标在已存在的内容的起始位置
* @param dend 光标在已存在的内容的结束位置
*
* @return 本次输入的内容
*/
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);
}
}
/**
* @return the maximum length enforced by this input filter
*/
public int getMax() {
return mMax;
}
}
看源码处理的逻辑思路,如下图;
int keep = mMax - (dest.length() - (dend - dstart));其中dest.length() - (dend - dstart)是计算当前文本中去掉将要被替换的字符后剩余的字符数;下图中,使用"第三个字"替换原始文本中的"之",这里限制了最大长度为5,keep最后的值就是5-(4-(2-1))=2
最后将会从输入的"第三个字"四个字符中截取0到2的字符保留,对选中的"之"进行替换,结果如下:
小结
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,int dstart, int dend)
- source:输入的内容
- start:输入的内容的起始位置,即0
- end:输入的内容的结束位置,也就是source的长度
- dest:已存在的内容
- dstart:光标在已存在的内容的起始位置
- dend:光标在已存在的内容的结束位置
使用InputFilter可以对文本的输入做一些特殊的判断和过滤操作,但是并不能完全的控制文本的内容。
对项目的中解决方式也就很简单了,直接copy了LengthFilter的代码,在截取超出文本的地方做相应的逻辑处理。
写下来就是整理一点点只是做一下记录,不对的地方还请大佬指正