EditText中的InputFilter导致文本重复。

23

我正在尝试实现一个EditText,限制输入为大写字符[A-Z0-9],包括数字。

我从一篇文章中开始使用了InputFilter方法,但在三星Galaxy Tab 2上出现了问题,在模拟器或Nexus 4上没有问题。

问题如下:

  1. 当我输入"A"时,文本显示为"A",很好
  2. 现在当我输入"B"时,文本应该是"AB",但它给我"AAB",看起来非常奇怪。

简而言之,它重复了字符。

以下是我正在使用的代码:

public class DemoFilter implements InputFilter {

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

        if (source.equals("")) { // for backspace
            return source;
        }
        if (source.toString().matches("[a-zA-Z0-9 ]*")) // put your constraints
                                                        // here
        {
            return source.toString().toUpperCase();
        }
        return "";
    }
}

XML文件代码:

<EditText
    android:id="@+id/et_licence_plate_1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="3"
    android:hint="0"
    android:imeOptions="actionNext"
    android:inputType="textNoSuggestions"
    android:maxLength="3"
    android:singleLine="true"
    android:textSize="18px" >
</EditText>

我完全被这个问题卡住了,所以任何帮助都将不胜感激。


我尝试了所有其他的解决方案,最终实现了它。请查看我的答案这里 - Shailendra Madda
10个回答

22

字符重复的问题源于InputFilter实现不当。如果替换不应该改变,请返回null:

@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
    boolean keepOriginal = true;
    StringBuilder sb = new StringBuilder(end - start);
    for (int i = start; i < end; i++) {
        char c = source.charAt(i);
        if (isCharAllowed(c)) // put your condition here
            sb.append(c);
        else
            keepOriginal = false;
    }
    if (keepOriginal)
        return null;
    else {
        if (source instanceof Spanned) {
            SpannableString sp = new SpannableString(sb);
            TextUtils.copySpansFrom((Spanned) source, start, end, null, sp, 0);
            return sp;
        } else {
            return sb;
        }           
    }
}

private boolean isCharAllowed(char c) {
    return Character.isUpperCase(c) || Character.isDigit(c);
}

这是正确的。请查看InputFilter.AllCaps的实现,它会将结果文本复制如此显示。 - Daniel Schuler
6
您可以尝试使用android:inputType="textVisiblePassword"来禁用自动完成功能,这是输入过滤器的真正问题所在。这可能不是最合适的方式,但它确实有效。干杯! - Martino Lessio
你的解决方案有效!只需要注意这个问题只出现在Android 7上,而不是在Android 6或Android 8上(至少对于我的应用程序来说)。 - Mike Keskinov
@MartinoLessio 先生,您刚刚通过这条评论节省了我很多时间。 - bko
@MartinoLessio,它有效。您能解释一下为什么以及它是如何起作用的吗? - S-Sh

3

我遇到了同样的问题,按照这里发布的解决方案修复后,带自动完成功能的键盘仍然存在一个未解决的问题。一种解决方法是将inputType设置为“visiblePassword”,但这会降低其功能性,不是吗?

我通过在filter()方法中返回非null结果时使用该调用来解决问题。

TextUtils.copySpansFrom((Spanned) source, start, newString.length(), null, newString, 0);

这将复制自动完成跨度到新结果中,并修复在选择自动完成建议时重复出现的奇怪行为。

1
谢谢你,复制Spans对我很有帮助,但是有时候我的应用程序会崩溃,因为“source”并不总是“Spanned”。所以我的解决方案是:if (source instanceof Spanned) { TextUtils.copySpansFrom((Spanned) source, start, newString.length(), null, newString, 0); } return newString; - Robyer

3

我在 Android 的 InputFilter 中发现了许多错误,我不确定这些是错误还是故意设定的。但明显它们不能满足我的要求。因此,我选择使用 TextWatcher 而不是 InputFilter。

private String newStr = "";

myEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // Do nothing
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            String str = s.toString();
            if (str.isEmpty()) {
                myEditText.append(newStr);
                newStr = "";
            } else if (!str.equals(newStr)) {
                // Replace the regex as per requirement
                newStr = str.replaceAll("[^A-Z0-9]", "");
                myEditText.setText("");
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
            // Do nothing
        }
    });

以上代码不允许用户在您的EditText中输入任何特殊符号。只允许大写字母和数字字符。


2
同样的问题也发生在我身上,InputFilter会复制字符。这是我使用过的解决方法:

Kotlin 版本:

private fun replaceInvalidCharacters(value: String) = value.replace("[a-zA-Z0-9 ]*".toRegex(), "")

textView.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}

    override fun afterTextChanged(s: Editable) {}

    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
        val newValue = replaceInvalidCharacters(s.toString())
        if (newValue != s.toString()) {
            textView.setText(newValue)
            textView.setSelection(textView.text.length)
        }
    }
})

运作良好。


这是一个好的答案,但是当复制粘贴时它将无法过滤其他特殊符号或表情符号。如果你想要更优雅的答案,请看这里:https://dev59.com/V2Yr5IYBdhLWcg3wQYJa#70359564 - Shailendra Madda

2
下面的解决方案还支持自动完成键盘选项。
editTextFreeNote.addTextChangedListener( new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            String newStr = s.toString();
            newStr = newStr.replaceAll( "[a-zA-Z0-9 ]*", "" );
            if(!s.toString().equals( newStr )) {
                editTextFreeNote.setText( newStr );
                editTextFreeNote.setSelection(editTextFreeNote.getText().length());
            }
        }

        @Override
        public void afterTextChanged(Editable s) {}
    } );

2

InputFilters可以附加到可编辑的S上,以限制对它们所做的更改。 请注意,它强调的是对其进行的更改,而不是整个文本。

按照以下方式操作...

 public class DemoFilter implements InputFilter {

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

            if (source.equals("")) { // for backspace
                return source;
            }
            if (source.toString().matches("[a-zA-Z0-9 ]*")) // put your constraints
                                                            // here
            {
               char[] ch = new char[end - start];

              TextUtils.getChars(source, start, end, ch, 0);

                // make the characters uppercase
                String retChar = new String(ch).toUpperCase();
                return retChar;
            }
            return "";
        }
    }

哦,我检查了这里发布的代码,它按预期工作,你能否也发布一下你在editText中添加此DemoFilter的xml文件实现? - CRUSADER
在我的情况下,Nexus 4和模拟器也能正常工作。但是在三星Galaxy Tab 2上无法工作。当键盘设置中关闭建议时,它可以在Tab 2上工作。因此,是否有任何XML布局文件中的属性,以便我们可以强制停止建议? - sam_k

0

这里大多数答案的问题在于它们都会弄乱光标位置。

  • 如果你只是简单地替换文本,那么你的光标就会停留在下一个输入字符的错误位置。
  • 如果你认为通过将光标放回末尾来解决这个问题,那么他们就无法添加前缀文本或中间文本,每次输入字符时都会被跳回到末尾,这是一种糟糕的体验。

你有一种简单的方法来处理它,还有一种更通用的方法来处理它。

简单的方法

          <EditText
            android:id="@+id/itemNameEditText"
            android:text="@={viewModel.selectedCartItemModel.customName}"
            android:digits="abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
            android:inputType="textVisiblePassword"/>

完成!

可见密码将解决双重回调和类似问题。这种解决方案的问题是它会删除您的建议和自动完成等内容。因此,如果您可以避免这个方向,请尽量避免!这将消除许多头痛,试图处理硬编码方式的每个可能问题。

硬编码方式

问题与输入过滤器回调结构与自动完成相关。很容易复现。只需将inputType设置为text,然后键入abc@,您将看到它被调用两次,如果您尝试忽略@,则最终可能得到abcabc而不仅仅是abc。

  1. 首先要处理的是删除操作,为此,您必须返回null以接受"",因为这是由删除触发的。
  2. 第二件要处理的事情是长按删除键,它会定期更新,但可能会作为一长串字符出现,因此在替换文本之前需要检查文本长度是否缩短,否则在长按删除键时可能会重复文本。
  3. 第三件要处理的事情是重复回调,通过跟踪上一个文本更改调用来避免重复获取。不用担心,您仍然可以连续输入相同的字母。

以下是我的示例。它并不完美,还有一些问题需要解决,但这是一个很好的起点。

下面的示例使用数据绑定,但如果您喜欢,也可以只使用intentFilter而不使用数据绑定。简化UI,仅显示重要部分。

在此示例中,我仅限于使用字母、数字和空格。我曾经在安卓键盘上疯狂敲击时导致分号出现过一次。因此,我认为仍然需要进行一些微调。

免责声明

--我没有测试自动完成功能

--我还没有尝试过建议

--我还没有测试过复制/粘贴

--这个解决方案是一个90%的解决方案,可以帮助你,但不是经过实战检验的解决方案

XML文件

<layout
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    >

<EditText
     bind:allowAlphaNumericOnly="@{true}

对象文件

@JvmStatic
@BindingAdapter("allowAlphaNumericOnly")
fun restrictTextToAlphaNumericOnly(editText: EditText, value: Boolean) {
    val tagMap = HashMap<String, String>()
    val lastChange = "repeatCheck"
    val lastKnownSize = "handleHoldingDelete"

    if (value) {
        val filter = InputFilter { source, start, end, dest, dstart, dend ->
            val lastKnownChange = tagMap[lastChange]
            val lastKnownLength = tagMap[lastKnownSize]?.toInt()?: 0

            //handle delete
            if (source.isEmpty() || editText.text.length < lastKnownLength) {
                return@InputFilter null
            }

            //duplicate callback handling, Android OS issue
            if (source.toString() == lastKnownChange) {
                return@InputFilter ""
            }

            //handle characters that are not number, letter, or space
            val sb = StringBuilder()
            for (i in start until end) {
                if (Character.isLetter(source[i]) || Character.isSpaceChar(source[i]) || Character.isDigit(source[i])) {
                    sb.append(source[i])
                }
            }

            tagMap[lastChange] = source.toString()
            tagMap[lastKnownSize] = editText.text.length.toString()

            return@InputFilter sb.toString()
        }

        editText.filters = arrayOf(filter)
    }
}

0

最近我遇到了同样的问题,问题的原因是……如果输入字符串没有改变,则不返回源字符串而返回 null,某些设备无法正确处理这种情况,因此字符会重复。

在您的代码中,您正在返回

return source.toString().toUpperCase();

不要返回这个,用 return null; 替换 return source.toString().toUpperCase();,但这只是一个临时修补,无法处理所有情况。对于所有情况,您可以使用此代码。

public class SpecialCharacterInputFilter implements InputFilter {

    private static final String PATTERN = "[^A-Za-z0-9]";
    // if you want to allow space use this pattern
    //private static final String PATTERN = "[^A-Za-z\\s]";

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        // Only keep characters that are letters and digits
        String str = source.toString();
        str = str.replaceAll(PATTERN, AppConstants.EMPTY_STRING);
        return str.length() == source.length() ? null : str;
    }
}

这段代码在做什么?它使用了一个正则表达式来查找除字母和数字以外的所有字符,然后将所有字符替换为空字符串,最终剩下的字符串只包含字母和数字。


0

试试这个:

class CustomInputFilter implements InputFilter {
    StringBuilder sb = new StringBuilder();

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        Log.d(TAG, "filter " + source + " " + start + " " + end + " dest " + dest + " " + dstart + " " + dend);
        sb.setLength(0);
        for (int i = start; i < end; i++) {
            char c = source.charAt(i);
            if (Character.isUpperCase(c) || Character.isDigit(c) || c == ' ') {
                sb.append(c);
            } else
            if (Character.isLowerCase(c)) {
                sb.append(Character.toUpperCase(c));
            }
        }
        return sb;
    }
}

这也允许在 filter() 方法一次接受多个字符时进行过滤,例如来自剪贴板的粘贴文本


0
我之前遇到过这个问题。 在xml中设置某些输入类型可能是问题的根源。 为了解决它,不需要在InputFilterTextWatcher中添加任何额外的逻辑,只需在代码中设置输入类型,就像这样: editText.setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接