maxLength属性不起作用了?可能是与InputFilter冲突了

我们都知道,在做登录时,通常需要对用户名和密码的长度进行限制。而在做长度限制时最简单的方法就是在EditText(或者TextView)中设置maxLength属性。前段时间做了一个项目,需要做登录功能,在对EditText输入长度限制时就用到了这个属性。后来需求又稍有变动,要求在输入时不接受空格和换行,也很简单,对输入框加上输入过滤就行了,如下:

@Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        if (source.equals(" ") || source.equals("\n"))
            return "";
        else
            return null;
    }

这里是让Activity实现InputFilter接口。然后给EditText绑定上该过滤器:

et.setFilters(new InputFilter[]{this});

做完这些,发现输入过滤是可以了,但长度限制居然不再起作用了。确定maxLength属性还在,猜测可能和刚做的输入过滤有关,于是屏蔽掉输入过滤绑定代码,再试长度限制就有效了,原来是maxLength属性和InputFilter冲突了。这又是为什么呢?我们注意到在过滤的方法中有几个int类型的参数,于是猜测应该和长度有关,根据几个参数字面上的意思应该是和end或者dend有关,但具体使用哪一个呢?我们看下这两个参数的说明吧:

This method is called when the buffer is going to replace the
* range dstart … dend of dest
* with the new text from the range start … end
* of source.

大意是说过滤的方法是用resource中的startend之间的输入替换掉bufferdstartdend之间的内容。什么意思呢?其实就是说我们要的输入结果中的第dstartdend位就是一次输入的第startend位。显然dend就输入结果的长度,所以我们要用这个参数对结果长度进行限制,于是把上面的过滤条件修改为:

if (source.equals(" ") || source.equals("\n")||dend>17)
            return "";

运行,果然限制输入长度为18(0-17)个字符。但好奇心强的我并不满足于此,同样为int类型的参数end是用什么用呢?而且在后来的输入测试中发现上面的限制条件也不是完美的,当我们用从别的地方复制内容粘贴到这个输入框时,会发现如果一次性复制的字符串长度大于18位时,也是可以完全粘贴上去的,甚至在连续输入状态下,字符串长度超过限制时同样是可以输入的(也许这里我表达的不太清楚,通俗的说就是,先打出一长串的字符(超过限制),然后点一下就会都显示到输入框了)。那会不会和没用到的参数end有关呢?接着测试,把过滤条件改为:

if (source.equals(" ") || source.equals("\n")||dend>17||end>3)
            return "";

运行,输入,发现当一次性输入或者复制的内容超过3个字符时,输入都是无效的。现在终于明白了,原来end是限制每次输入长度的,而dend是限制总的输入长度的!(之所以设置输入条件的2个参数的大小差别比较大主要是为了易于区分,如果把它们设置一样大小,那即便有效果也不容易弄清楚是哪个参数起的作用)

现在我们才只是弄清楚了那2个各自的作用,我们的主要疑惑,为什么maxLength属性不起作用了还是没弄清楚,还要接着往下找原因。既然是在布局文件中出现的属性,必然和源码有关,我们去看下EditText的源码,发现它是继承自TextView的,跟着去到TextView的源码,可以看到在构造方法中有关于InputFilter的设定:

if (maxlength >= 0) {
    setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
} else {
    setFilters(NO_FILTERS);
}

很容易理解,maxlength就是最大字符串长度,初始值为:

int maxlength = -1;

maxLength属性中正是与之对应。从源码中可以看出,如果我们设置了maxLength属性且为正整数,那就会执行setFilters(new InputFilter[] { newInputFilter.LengthFilter(maxlength) });从字面不难看出就是给它绑定长度过滤,我们来看下LengthFilter的实现:

/**
 * This filter will constrain edits not to make the length of the text
 * greater than the specified length.
 */
public static class LengthFilter implements InputFilter {
    private final int mMax;

    public LengthFilter(int max) {
        mMax = max;
    }

    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);
        }
    }

不难理解。而且这里也从原理上印证了我们对于过滤方法中的参数dendend区别的理解。

接着,如果我们没有设置了maxLength属性(不可以设置小于0的值),就会执行第二句:
setFilters(NO_FILTERS);
NO_FILTERS是个什么东西呢?看源码:
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
这个的 InputFilter[0] 就是没有限制的长度过滤器。

至此,我们终于明白了,原来maxLength属性也是通过InputFilter来实现的,如果我们在代码里给TextView绑定了新的InputFilter,就会覆盖掉maxLength属性,导致无效。

最后,解决办法当然也很明了,比较简单的方法是我们只实现对空格和换行的过滤,长度过滤用系统的LengthFilter,绑定时如下:
et.setFilters(new InputFilter[]{new LengthFilter(10)});
最后,系统的这个LengthFilter和我们自己写的判断条件“ dend>17||end>3”有什么区别呢?区别就是,按照我们自己加的判断条件,当一次性输入长度大于end时,则整个输入都无效。举例来说就是,每次输入限制为5(end>5),假如已经输入16位,这时我们一次性输入大于5个字符时,则整个本次输入都无效,即一个字符也没输入;假设我们限制总的输入长度为17(dend>17),已经输入了17位,再一次性输入5位字符,则可以输入17+5=22位;而系统的长度过滤器则会把这次输入的5位中的第1个输入,使输入长度正好18位,余下的字符串才被过滤掉。

你可能感兴趣的:(移动开发,Android)