项目需求讨论--可能是用InputFilter来做的最好的金额限制

4,287 阅读9分钟

看惯了可能是XXX最好的,可能是XXXX目前最好的,今天我也用下这个标题,哈哈。别喷我,当然我也就吹吹牛。有很多好的方法来实现。

本文主要还是用来讲解下InputFilter的使用。

一般金额类的输入需求比较多,我们这里就用金额输入框做实例。其他的类似的文字,大小写字母等需求限制也是同理的。

某天产品经理 A拿着菜刀到我身边说:

第一次交锋

A:小B啊,这个界面的金额输入框输入的钱小数点后最多二位,也就是最多到分,还有那个界面的这个地方,填金额也是精度到分。

唯唯诺诺的我:好的,马上完成。

1.控制小数点后位数:

因为有很多界面都要用到,所以我们专门抽出一个类来进行控制,并且我们知道,我们要控制EditText控制它的输入内容,其实相当于是对其进行过滤,所以我们让我们的类实现InputFilter接口。

public class PointLengthFilter implements InputFilter {
    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        return null;
    }
}

发现一定要我们实现filter方法,从字面意思看我们也知道是过滤,那我们来看下具体的参数字段的意思:

字段 类型 内容
source CharSequence 为即将输入的字符串
start int source的start, start 为0
end int source的end ,因为start为0,end也可理解为source长度了
dest Spanned 输入框中原来的内容
dstart int 要替换或者添加的起始位置,即光标所在的位置
dend int 要替换或者添加的终止始位置,若为选择一串字符串进行更改,则为选中字符串 最后一个字符在dest中的位置

我们来假设下,我们通过键盘依次输入12345,我们可以看到相应的值:

source:1,start:0,end:1,dest:,dstart:0,dend:0
source:2,start:0,end:1,dest:1,dstart:1,dend:1
source:3,start:0,end:1,dest:12,dstart:2,dend:2
source:4,start:0,end:1,dest:123,dstart:3,dend:3
source:5,start:0,end:1,dest:1234,dstart:4,dend:4

大家可能会发现start一直为0,end一直为1,因为我们是依次输入的,比如你复制三个字符,通过粘贴复制的方式加入到EditText中,这时候就不是0和1了,而是0,3。

所以根据这个小数点位数需求,我们先来第一版的Filter(有问题版本)

public class PointLengthFilter implements InputFilter {

    private static final int DECIMAL_DIGITS = 2;//小数的位数

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        // 删除等特殊字符,直接返回
        if ("".equals(source.toString())) {
            return null;
        }
        //原来输入框已有的内容
        String dValue = dest.toString();
        //通过小数点来进行拆分,分为小数点前面的字符串和小数点后面的字符串
        String[] splitArray = dValue.split("\\.");
        if (splitArray.length > 1) {
            //获的小数点后面的字符串
            String dotValue = splitArray[1];
            //判断小数点后面的当前位数是不是已经大于等于规定的2了
            //如果已经2位了,则返回"";
            if (dotValue.length() >= DECIMAL_DIGITS) {
                return "";
            }
        }
        return null;
    }
}

然后在我们的Activity中设置:

EditText editText = (EditText) findViewById(R.id.et_money);
editText.setFilters(new InputFilter[]{new PointInputFilter()});

PS :可以把过滤的条件单独写出来分为好几个文件,因为传入的是InputFilter数组。

这样。我们终于实现了小数点后面的位数控制了。

如下图所示,我们输入12345.67之后,再输入其他字符,在filter中就默认返回了一个空的字符串"",所以就等于没输入其他内容进去。


第二次交锋:

产品经理 A拿着菜刀气冲冲的过来了。

A:你这个输入金额的有问题你知道吗?你都不能自己好好测试测试吗?

低声下气的我:不可能啊,我测试过的啊,小数点后面的数的位数不会超过2啊。

A:位数的确不超过2了,但是你修改下小数点前面的数字试试。

我拿着手机试了下,比如上面我们已经输入了12345.67,这时候我想在小数点前面的内容多加个数字,或者前面的12345我删了几个,再输入其他数字都不行了。因为小数点后的位数是一直是2,这时候我们修改小数点前的内容,就一直触发:

String[] splitArray = dValue.split("\\.");
        if (splitArray.length > 1) {
            //获的小数点后面的字符串
            String dotValue = splitArray[1];
            //判断小数点后面的当前位数是不是已经大于等于规定的2了
            //如果已经2位了,则返回"";
            if (dotValue.length() >= DECIMAL_DIGITS) {
                return "";
            }
        }

所以一直返回一个空字符串,所以就无法修改小数点前面的数字了。

2.控制小数点位数的同时,更改小数点前的数字:

我们只需要改原本控制小数点的逻辑代码即可:

String dValue = dest.toString();
String[] splitArray = dValue.split("\\.");
if (splitArray.length > 1) {
    String dotValue = splitArray[1];
    //获取小数点“.”在字符串中的index值
    int dotIndex = dValue.indexOf(".");
    /添加了一个条件判断:输入光标是在小数点的后面
    if (dotValue.length() >= DECIMAL_DIGITS && dstart > dotIndex) {
        return "";
    }
}

试了一下。果然可以自由的对小数点前面的数字随意的增删改了。哈哈。我心满意足的再次改好上交了。


第三次交锋:

产品经理这次拿着一把砍刀再次过来。

A:你这个输入金额的小数点前面的数可以输入很多,我这边考虑了下,要求小数点前面最多输入6位,加起来最大可输入的值是999999.99元。也就是不超过一百万,下班前记得完成哦。

有点气愤的我:好的。包您满意。

3.限制小数点前面的位数:

这时候其实我们也知道并不难,只要在小数点前面的位数增加控制就行:

public class PointInputFilter1 implements InputFilter {

    private static final int DECIMAL_DIGITS = 2;//小数的位数
    private static final int INTEGER_DIGITS = 6;//整数位的位数
    private static final int TOTAL_DIGITS = 9; //整数部分 + “小数点” + 小数部分
    private int currentLimitDigits = INTEGER_DIGITS;//当前控制的位数值

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {

        // 删除等特殊字符,直接返回
        if ("".equals(source.toString())) {
            return null;
        }
        String dValue = dest.toString();
        String[] splitArray = dValue.split("\\.");
        switch (splitArray.length) {
            case 1:
                //判断当前是否有小数点,如果有小数点就把控制位数变为TOTAL_DIGITS(其实只要比整数位数+1大就可以)
                if (dValue.indexOf(".") != -1) {
                    currentLimitDigits = TOTAL_DIGITS;
                } else {
                    //如果没有小数点,则继续控制前面整数位的位数为6位
                    currentLimitDigits = INTEGER_DIGITS;
                }

                /**这里如果我们直接输入999999时候,其实已经不能按其他数字了,
                不然就超过一百万了,但是这时候如果输入的是小数点,则可以在输入框中显示小数点。
                而且这时候在上面已经把当前的位数限制变大,
                这时候就可以就可以输入其他数字,然后接下去就会跳入到下面的case 2的判断了。
                **/
                if(splitArray[0].length() >= currentLimitDigits && !".".equals(source.toString())){
                    return "";
                }
                break;

            case 2:
                String integerValue = splitArray[0];
                String dotValue = splitArray[1];
                int dotIndex = dValue.indexOf(".");
                if (integerValue.length() >= INTEGER_DIGITS && dstart <= dotIndex) {
                    return "";
                }
                if (dotValue.length() >= DECIMAL_DIGITS && dstart > dotIndex) {
                    return "";
                }
                break;
            default:
                break;
        }

        return null;
    }
}

这下终于可以交差了。然后沾沾自喜的把成果发布了一版,开心的等着下班。


第四次交锋:

产品经理推着大炮再次走了过来,

A:你的输入有问题,你看,我都输入了好几百万了。

我:不可能啊,我测试过的啊,我演示给你看,看吧。不可能输得进去的。

A:我不是键盘输入的,我是直接其他地方复制了多位数字,然后粘贴复制进去的。

我: ........

A:反正我不管,你没弄完不准下班。

我心里暗暗说了句:MMP

4.处理通过粘贴复制的方式输入

这里我们可以有二种处理方式:

  1. 直接就干脆不让多位数字粘贴进来。
  2. 针对多位数字赋值粘贴来进行处理。

<1> 不准复制粘贴多位数字:

这个很简单,如果客户是复制一位数字,然后粘贴复制进去的,其实就等效我们用键盘输入,所以就不需要特殊处理。我们只在意的是比如现在是999.99,他在前面直接粘贴了99999。就变成了99999999.99了,超过我们的范围了。我们可以直接禁止多位数字的粘贴复制,代码很简单:

//在最前面多添加一个判断,就是当输入的字符是多位的时候,直接返回空字符串。
//因为通过键盘输入我们都是一位位输入的,而多位的情况一般就是复制粘贴进来的。
if(source.length() > 1){
    return "";
}

<2> 处理粘贴复制的方式的输入:

我们假设这几种情况:

(1)输入框里面的内容是整数,比如1234,然后我们复制整数9999进去,这时候应该是123499。
(2)输入框里面的内容是整数,比如1234,然后我们复制整数999.999进去,这时候应该是123499.99。
(3)输入框里面的内容是小数,比如1234.1,然后我们复制整数999进去,如果复制在小数点前面,应该是123499.1,如果复制在小数点后面,应该是1234.1。
(4)输入框里面的内容是小数,比如1234.1,然后我们复制的也是小数进去,比如9.9,我们粘贴在小数点前,则变为了123499.1,因为输入框内默认就一个小数点,复制进来的9.9我们就作为99加入到整数部分。如果加到小数点后面则变为1234.19。
(5)输入框里面是空的内容,我们输入12345678.87654321;小数点前面也超出,后面也超出,取有效部分,变为123456.87。

PS:每个人在具体的业务中可能要求不同,主要是按实际业务来,我这边是当粘贴的数字太大的时候,截取了还能放下的位数,你也可以干脆发现粘贴的数加进去后超标了。直接返回空字符串。

附上最终的代码:

PointInputFilter.java:

public class PointInputFilter implements InputFilter {

    private static final int DECIMAL_DIGITS = 2;//小数的位数
    private static final int INTEGER_DIGITS = 6;//整数位的位数
    private static final int TOTAL_DIGITS = 9; //整数部分 + “小数点” + 小数部分
    private int currentLimitDigits = INTEGER_DIGITS;

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {

        // 删除等特殊字符,直接返回
        if ("".equals(source.toString())) {
            return null;
        }

        /*
        如果想要直接禁止复制粘贴多个数字,直接这边限制。
        if(source.length() > 1){
            return "";
        }*/

        String dValue = dest.toString();
        String[] splitArray = dValue.split("\\.");
        switch (splitArray.length) {
            case 1:

                if (dValue.indexOf(".") != -1) {
                    currentLimitDigits = TOTAL_DIGITS;
                } else {
                    currentLimitDigits = INTEGER_DIGITS;
                }

                if (source.length() > 1) {

                    String sValue = source.toString();
                    String[] subSplitArray = sValue.split("\\.");
                    switch (subSplitArray.length) {
                        case 1:
                            if (source.length() + dest.length() > currentLimitDigits) {
                                return source.subSequence(0, currentLimitDigits - dest.length());
                            }
                            break;
                        case 2:
                            String content = "";

                            if (dstart == dest.length()) {
                                if (subSplitArray[0].length() + dest.length() > INTEGER_DIGITS) {
                                    content += subSplitArray[0].subSequence(0, INTEGER_DIGITS - dest.length());
                                } else {
                                    content += subSplitArray[0];
                                }

                                if (subSplitArray[1].length() > DECIMAL_DIGITS) {
                                    content += "."+ subSplitArray[1].substring(0, DECIMAL_DIGITS);

                                } else {
                                    content += "."+ subSplitArray[1];
                                }
                                return content;

                            } else {
                                if (subSplitArray[0].length() + dest.length() > INTEGER_DIGITS) {
                                    content += subSplitArray[0].subSequence(0, INTEGER_DIGITS - dest.length());
                                } else {
                                    content += subSplitArray[0];
                                }
                            }
                            return content;

                        default:
                            break;
                    }

                }

                if (splitArray[0].length() >= currentLimitDigits && !".".equals(source.toString())) {
                    return "";
                }

                break;

            case 2:
                String integerValue = splitArray[0];
                String dotValue = splitArray[1];
                int dotIndex = dValue.indexOf(".");

                if (dstart <= dotIndex) {

                    if (integerValue.length() >= INTEGER_DIGITS) {
                        return "";
                    } else if (source.length() + integerValue.length() >= INTEGER_DIGITS) {
                        return source.subSequence(0, INTEGER_DIGITS - integerValue.length());
                    }

                } else {

                    if (dotValue.length() >= DECIMAL_DIGITS) {
                        return "";
                    } else if (source.length() + dotValue.length() >= DECIMAL_DIGITS) {
                        return source.subSequence(0, DECIMAL_DIGITS - dotValue.length());
                    }
                }

                break;
            default:
                break;
        }

        return null;
    }
}

总结:

完毕。终于可以安心下班了。哈哈。。。