android 用于两个大写字母和一个或两个数字的InputFilter,仅当第一个输入是数字时才有效

bq8i3lrv  于 2023-03-06  发布在  Android
关注(0)|答案(4)|浏览(130)
    • bounty将在2天后过期**。回答此问题可获得+50声望奖励。dentex希望引起更多人关注此问题。

我需要将可以输入到TextInputEditText中的输入限制为两个大写字母和一两个数字,如AB01、CS8、XY99或ND5。
所以我写了这个方法

/**
 * @return an InputFilter that restricts input to the format of two capital letters and one or two digits
 */
public static InputFilter getClientIdInputFilter() {
    return (source, start, end, dest, dstart, dend) -> {
        Log.v(DEBUG_TAG, "----\nsource: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
        String destTxt = dest.toString();
        String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
        Log.v(DEBUG_TAG, input);
        if (input.matches("^[A-Z]{0,2}[0-9]{0,2}$")) {
            if (input.length() == 1 && input.matches("^[0-9]$")) {
                Log.v(DEBUG_TAG, "Invalid input A");
                return "";
            }
            if (input.length() == 2 && !input.matches("^[A-Z]{2}$")) {
                Log.v(DEBUG_TAG, "Invalid input B");
                return "";
            }
            Log.v(DEBUG_TAG, "Valid input");
            return null;
        }
        Log.v(DEBUG_TAG, "Invalid input C");
        return "";
    };
}

我是这样用的:

TextInputEditText clientId = dialogView.findViewById(R.id.edit_text_client_id);
InputFilter[] clientIdFilters = new InputFilter[1];
clientIdFilters[0] = InputFilterUtils.getClientIdInputFilter();
clientId.setFilters(clientIdFilters);

它 * 几乎 * 工作完美...
问题是only当从空的textfield开始输入时,在前两个大写字母之后(所以在第三个字母处),该字段被完全删除。相反,如果先输入数字(并且被正确地忽略),如果继续输入字母,当到达第三个字母时,这一次前两个字母保留,用户可以继续输入数字...
我对这种行为没有任何线索(我添加了一些日志条目,但无论如何都不清楚)

cvxl0en2

cvxl0en21#

为什么要用regex来做这个呢?这是一个简单的验证逻辑。

public static InputFilter getClientIdInputFilter() {
    return (source, start, end, dest, dstart, dend) -> {
        Log.v(DEBUG_TAG, "----\nsource: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
        String destTxt = dest.toString();
        String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
        Log.v(DEBUG_TAG, input);
        
        // changes start from here
        if (input.length() != 3 && input.length() != 4)
            return "";
        int numberOfCapitalLetters = 0, nuberOfDigits = 0;
        for (char i: input.toCharArray()) { // loop over all the letters
            if (i >= 'A' && i <= 'Z') // capital letter
                numberOfCapitalLetters += 1;
            else if (i >= '0' && i <= '9')
                nuberOfDigits += 1;
            else 
                return ""; // only capital letters and numbers allowed
        }
        if ((numberOfCapitalLetters == 2) && ((nuberOfDigits == 1) || (nuberOfDigits == 2)))
            return null; // valid
        else
            return "";
    };
}
57hvy0tb

57hvy0tb2#

如果你想使用regex方法使用下面。

Pattern pattern = Pattern.compile("[A-Z]{2}\\d{1,2}");
    
String input = dest.subSequence(0, dstart) + source.toString() + dest.subSequence(dend, dest.length());
                Matcher matcher = pattern.matcher(input);
                if (!matcher.matches()) {
                    return "";
                }
                return null;
fcipmucu

fcipmucu3#

我可以理解@xenon134的答案,它实际上并不检查第一个和第二个字符是否是字母,所以,由于输入应该限制在3或4个字符,我会使用一个更硬编码的解决方案,而不是过度设计,所以:

public static InputFilter getClientIdInputFilter() {
    return (source, start, end, dest, dstart, dend) -> {
        Log.v(DEBUG_TAG, "----\nsource: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
        String destTxt = dest.toString();
        String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
        Log.v(DEBUG_TAG, input);
        
        // changes start from here
        if (input.length() != 3 && input.length() != 4)
            return "";

        char ch1 = input.charAt(0);
        char ch2 = input.charAt(1);
        char ch3 = input.charAt(2);

        if(ch1 < 'A' || ch1 > 'Z' || ch2 < 'A' || ch2 > 'Z') 
            return "";

        if(ch3 < '0' || ch3 > '9') 
            return "";

        if(input.length() == 4) {
            char ch4 = input.charAt(3);
            if(ch4 < '0' || ch4 > '9') 
                return "";
        }
        
        return null; //valid
    };
}
  • 请原谅我忘记了任何分号,因为我现在正在使用Kotlin有一段时间了,也不确定我是否可以像Java中那样通过索引获得字符,您可能需要使用input.charAt(index)
  • 此外,您可以提取一些代码,在单独的函数上检查字符是字母还是数字,这样代码看起来更可读 *:
  • public static boolean isLetter(char c) { ... }
  • public static boolean isNumber(char c) { ... }
bsxbgnwa

bsxbgnwa4#

实际上问题不在代码中;它在Google Pixel 3上的表现和预期完全一样可能所有的谷歌设备都会有同样的结果。
但是,当在三星设备上尝试时,这个空的TextInputEditText行为开始出现。
This answer证明了三星设备的另一个问题。
因此,InputFilter的行为因设备/制造商而异,这可能与软输入键盘有关,而不是制造商。
无论如何,这需要以不同于InputFilter所期望的方式来处理;下面是我们发现的处理方法:

    • 第一名:**
      • 在Google上:**source变量只返回最后一个输入字符。
      • 在Samsung上:**source变量返回整个字符串。
    • 第二次:**
      • 在Google上:**return ""将最后一个输入字符替换为""(如预期)。
      • 在Samsung上:**return ""将整个输入字符串替换为""(这就是TextInputEditText重置为空字符串的原因)。

有了这些观察结果,我们可以通过检查source的长度来修复这个问题。如果它是1,那么这是正常的行为,所以返回"";否则,我们需要返回String的匹配部分,而不是""
一个奇怪的情况发生时,开始了一个数字,这将是正常的封锁;但是当你之后打一封信的时候则发送该数字而不是该字母。2这可以通过在返回字符串之前删除前导数字来处理。
进行这些更改后,以下是修改后的版本:

public static InputFilter getClientIdInputFilter() {

    return (source, start, end, dest, dstart, dend) -> {
        if (end > start) {
            Log.v(DEBUG_TAG, "----\n source: " + source + ", start: " + start + ", end: " + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend);
            String destTxt = dest.toString();
            String input = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
            Log.v(DEBUG_TAG, input);

            String pattern = "^[A-Z]{0,2}\\d{0,2}";

            if (input.matches(pattern)) {
                if (input.length() == 1 && input.matches("^\\d$")) {
                    Log.v(DEBUG_TAG, "Invalid input A");
                    return "";
                }
                if (input.length() == 2 && !input.matches("^[A-Z]{2}$")) {
                    Log.v(DEBUG_TAG, "Invalid input B");
                    if (source.length() == 1) return "";
                    return source.subSequence(0, countMatches("^[A-Z]$", 
                                               String.valueOf(input.charAt(0))));
                }
                Log.v(DEBUG_TAG, "Valid input");
                return null;
            }

            Log.v(DEBUG_TAG, "Invalid input C");
            if (source.length() == 1) return "";

            // remove leading digits
            int index = 0;
            for (char c : input.toCharArray()) {
                if (Character.isDigit(c))
                    index++;
                else
                    break;
            }

            return source.subSequence(index, index + 
                                    countMatches(pattern, input.substring(index)));

        }

        return null;
    };
}

/**
 * @return how many chars match the given pattern
 * Credits: <a href="https://stackoverflow.com/a/43267517/9851608"></a>
 */
private static int countMatches(String pattern, String input) {
    Pattern p = Pattern.compile(pattern);
    Matcher m = p.matcher(input);
    int count = 0;
    while (m.find()) count += m.group().length();
    return count;
}

相关问题