【问题标题】:InputFilter on EditText cause repeating textEditText 上的 InputFilter 导致重复文本
【发布时间】:2013-09-02 22:54:41
【问题描述】:

我正在尝试实现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>

我完全坚持这一点,所以这里的任何帮助将不胜感激。

【问题讨论】:

  • 我尝试了所有其他解决方案并最终实现了它。看我的回答here

标签: android android-edittext android-input-filter


【解决方案1】:

字符重复的问题来自 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 的实现,它复制结果文本,如下所示。
  • 您可以尝试使用 android:inputType="textVisiblePassword" 来禁用输入过滤器的真正问题的自动完成功能。这不是正确的方法,但它确实有效。干杯!
  • 您的解决方案有效!只需要注意这个问题只出现在 Android 7 上,而不会出现在 Android 6 或 Android 8 上(至少对于我的应用而言)。
  • @MartinoLessio 先生,您的这条评论为我节省了很多时间
  • @MartinoLessio,它有效。你能解释一下它为什么以及如何欺骗吗?
【解决方案2】:

InputFilters 可以附加到 Editable 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 "";
        }
    }

【讨论】:

  • 嗯,我检查了这里发布的代码,它按预期工作,您是否也可以发布您要添加此 DemoFilter 的 editText 的 xml 文件实现..
  • 在我的情况下也可以在nexus 4和模拟器上正常工作。但是在三星 Galax Tab 2 上没有工作。当键盘设置中的建议关闭时,它在 Tab 2 上工作。所以xml布局文件中的任何属性,所以我们可以强制停止建议。
【解决方案3】:

我遇到了同样的问题,在使用此处发布的解决方案进行修复后,具有自动完成功能的键盘仍然存在问题。一种解决方案是将 inputType 设置为“visiblePassword”,但这会减少功能,不是吗?

我能够通过在filter() 方法中返回非空结果时使用调用来解决此问题

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

这会将自动完成范围复制到新结果中,并修复选择自动完成建议时重复的奇怪行为。

【讨论】:

  • 谢谢你,复制 Spans 为我解决了这个问题,但它有时会使我的应用程序崩溃,因为 source 并不总是 Spanned。所以我的解决方案是:if (source instanceof Spanned) { TextUtils.copySpansFrom((Spanned) source, start, newString.length(), null, newString, 0); } return newString;
【解决方案4】:

我在 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 中键入任何特殊符号。只允许使用大写字母数字字符。

【讨论】:

    【解决方案5】:

    以下解决方案还支持自动完成键盘选项

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

    【讨论】:

      【解决方案6】:

      我也一样,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)
              }
          }
      })
      

      效果很好。

      【讨论】:

      • 这是一个很好的答案,但它不会在复制粘贴时过滤其他特殊符号或表情符号。如果您想要更优雅的答案,请查看here
      【解决方案7】:

      试试这个:

      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() 方法一次接受多个字符时进行过滤,例如从剪贴板粘贴文本

      【讨论】:

        【解决方案8】:

        我之前遇到过几次这个问题。 在 xml 中设置某些类型的 inputTypes 是问题的根源。 要在 InputFilterTextWatcher 中不使用任何其他逻辑来解决它,只需在代码中设置输入类型,而不是像这样的 xml:

        editText.setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);

        【讨论】:

          【解决方案9】:

          最近我遇到了同样的问题 问题的原因是...如果输入字符串没有变化,则不返回源字符串返回 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;
              }
          }
          

          这段代码发生了什么,有一个正则表达式,我们将找到除字母和数字之外的所有字符,现在它将用空字符串替换所有字符,然后剩余的字符串将有字母和数字。

          【讨论】:

            【解决方案10】:

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

            • 如果您只是替换文本,您的光标会出现在下一个输入字符的错误位置
            • 如果您认为您通过将光标放回末尾来处理该问题,那么他们无法添加前缀文本或中间文本,它们总是在每个键入的字符处跳回末尾,这是一种糟糕的体验。

            您有一个简单的方法来处理它,并且有一个更通用的方法来处理它。

            简单的方法

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

            完成!

            可见密码将解决双重回调的问题和类似的问题。此解决方案的问题在于它会删除您的建议和自动完成功能以及类似的内容。因此,如果您可以摆脱这个方向,请这样做!!!它将消除尝试处理所有可能遇到的困难问题的麻烦,哈哈。

            艰难的道路

            该问题与自动完成触发的 inputfilter 回调结构有关。它很容易复制。只需设置您的 inputType = text,然后输入 abc@,您会看到它被调用了两次,并且如果您尝试忽略 @ 时最终可以使用 abcabc 而不是 abc。

            1. 您必须处理的第一件事是删除以执行此操作,您必须 返回 null 以接受由 delete 触发的 ""。
            2. 您必须处理的第二件事是按住 delete,因为它会经常更新,但可能会以一长串字符的形式出现,因此您需要在替换文本之前查看您的文本长度是否缩短了,否则您最终可能会重复按住删除的同时输入您的文本。
            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)
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2012-06-03
              • 2014-10-19
              • 2019-04-13
              • 1970-01-01
              • 2012-11-24
              • 2017-06-19
              • 2016-02-26
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多