【问题标题】:EditText and InputFilter cause repeating textEditText 和 InputFilter 导致重复文本
【发布时间】:2012-06-03 06:11:08
【问题描述】:

我正在尝试实现一个 EditText,它将输入限制为仅限 alpha 字符 [A-Za-z]。

我从this post 开始使用 InputFilter 方法。当我键入“a%”时,文本会消失,然后如果我按退格键,则文本是“a”。我已经尝试过过滤器功能的其他变体,例如使用正则表达式仅匹配 [A-Za-z] 有时会看到重复字符等疯狂行为,我将输入“a”然后输入“b”然后得到“aab”输入“c”得到“aabaabc”,然后按退格键得到“aabaabcaabaabc”!

这是我目前使用的不同方法的代码。

    EditText input = (EditText)findViewById( R.id.inputText );
    InputFilter filter = new InputFilter() {
        @Override
        public CharSequence filter( CharSequence source, int start, int end, Spanned dest, int dstart, int dend ) {
            //String data = source.toString();
            //String ret = null;
            /*
            boolean isValid = data.matches( "[A-Za-z]" );
            if( isValid ) {
                ret = null;
            }
            else {
                ret = data.replaceAll( "[@#$%^&*]", "" );
            }
            */
            /*
            dest = new SpannableStringBuilder();
            ret = data.replaceAll( "[@#$%^&*]", "" );
            return ret;
            */

            for( int i = start; i < end; i++ ) {
                if( !Character.isLetter( source.charAt( i ) ) ) {
                    return "";
                }
            }

            return null;
        }
    };
    input.setFilters( new InputFilter[]{ filter } );

我完全被这个难住了,所以这里的任何帮助都将不胜感激。

编辑: 好的,我已经对 InputFilter 进行了很多实验并得出了一些结论,尽管没有解决问题。请参阅下面我的代码中的 cmets。我现在要试试 Imran Rana 的解决方案。

    EditText input = (EditText)findViewById( R.id.inputText );
    InputFilter filter = new InputFilter() {
        // It is not clear what this function should return!
        // Docs say return null to allow the new char(s) and return "" to disallow
        // but the behavior when returning "" is inconsistent.
        // 
        // The source parameter is a SpannableStringBuilder if 1 char is entered but it 
        // equals the whole string from the EditText.
        // If more than one char is entered (as is the case with some keyboards that auto insert 
        // a space after certain chars) then the source param is a CharSequence and equals only 
        // the new chars.
        @Override
        public CharSequence filter( CharSequence source, int start, int end, Spanned dest, int dstart, int dend ) {
            String data = source.toString().substring( start, end );
            String retData = null;

            boolean isValid = data.matches( "[A-Za-z]+" );
            if( !isValid ) {
                if( source instanceof SpannableStringBuilder ) {
                    // This works until the next char is evaluated then you get repeats 
                    // (Enter "a" then "^" gives "a". Then enter "b" gives "aab")
                    retData = data.replaceAll( "[@#$%^&*']", "" );
                    // If I instead always returns an empty string here then the EditText is blanked.
                    // (Enter "a" then "^" gives "")
                    //retData = "";
                }
                else { // source is instanceof CharSequence
                    // We only get here if more than 1 char was entered (like "& ").
                    // And again, this works until the next char is evaluated then you get repeats 
                    // (Enter "a" then "& " gives "a". Then enter "b" gives "aab")
                    retData = "";
                }
            }

            return retData;
        }
    };
    input.setFilters( new InputFilter[]{ filter } );

【问题讨论】:

  • 带有.replaceAll() 的代码几乎是正确的。在String data = source.toString() 中使用完整输入字符串创建字符串导致的重复字符,而您应该处理Android 询问的子集,如String data = source.toString().substring(start,end)
  • 似乎我在这里遇到的问题是,当我一次输入 1 个字符时,开始始终为 0。因此 String data = source.toString().substring( start, end );总是给我全文,而不仅仅是改变了什么。
  • ...是的,刚刚尝试了来自 this post 的示例,并且 start 始终为 0,所以我也得到了重复的字符。
  • 我尝试了所有其他解决方案并最终实现了它。看我的回答here

标签: android android-edittext


【解决方案1】:

使用以下代码:

EditText input = (EditText) findViewById(R.id.inputText);
   input.addTextChangedListener(new TextWatcher() {

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // TODO Auto-generated method stub
         for( int i = start;i<s.toString().length(); i++ ) {
             if( !Character.isLetter(s.charAt( i ) ) ) {
                input.setText("");
             }
         }

    }

    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
        // TODO Auto-generated method stub

    }

    public void afterTextChanged(Editable s) {
        // TODO Auto-generated method stub

    }
   });

如果您希望有效文本保留在 EditText 中:


 input.addTextChangedListener(new TextWatcher() {

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // TODO Auto-generated method stub

    }

    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
        // TODO Auto-generated method stub

    }
    public void afterTextChanged(Editable s) {
        // TODO Auto-generated method stub
         for( int i = 0;i<s.toString().length(); i++ ) {
             if( !Character.isLetter(s.charAt( i ) ) ) {                    
                s.replace(i, i+1,"");               
             }
         }
    }
   });

【讨论】:

  • 这几乎对我有用。我希望有效文本保留在 EditText 中。因此,如果我输入“a”然后输入“b”然后输入“^”,我仍然希望在 EditText 中看到“ab”。当然,如果我用空字符串以外的任何内容调用 setText,它会无限调用 onTextChanged。
  • @Rooster242 查看我的编辑,如果您希望有效文本保留在 EditText 中,只需将上述代码添加到 afterTextChanged() 方法并删除以前的代码来自 onTextChanged() 方法
  • 啊,谢谢 Imran,效果很好。这两种方法实际上都有效(InputFilter 和 TextWatcher) 我决定使用 TextWatcher,因为它更容易看到发生了什么事情,而且我不需要在 replaceAll() 中使用黑名单。我将我自己的答案标记为解决方案,因为它是由 android:cursorVisible="false" 引起的重复字符,这真的让我感到困惑。我为它创建了一个错误报告,Issue 32475
  • 在输入过滤器上没有运气,而 TextWatcher 与 IME 配合得很好
【解决方案2】:

修复重复文本,适用于所有 Android 版本:

public static InputFilter getOnlyCharactersFilter() {
    return getCustomInputFilter(true, false, false);
}

public static InputFilter getCharactersAndDigitsFilter() {
    return getCustomInputFilter(true, true, false);
}

public static InputFilter getCustomInputFilter(final boolean allowCharacters, final boolean allowDigits, final boolean allowSpaceChar) {
    return new InputFilter() {
        @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)) {
                    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, sb.length(), null, sp, 0);
                    return sp;
                } else {
                    return sb;
                }
            }
        }

        private boolean isCharAllowed(char c) {
            if (Character.isLetter(c) && allowCharacters) {
                return true;
            }
            if (Character.isDigit(c) && allowDigits) {
                return true;
            }
            if (Character.isSpaceChar(c) && allowSpaceChar) {
                return true;
            }
            return false;
        }
    };
}

现在你可以像这样使用这个文件管理器:

 //Accept Characters Only
edit_text.setFilters(new InputFilter[]{getOnlyCharactersFilter()});

//Accept Digits and Characters
edit_text.setFilters(new InputFilter[]{getCharactersAndDigitsFilter()});

//Accept Digits and Characters and SpaceBar
edit_text.setFilters(new InputFilter[]{getCustomInputFilter(true,true,true)});

【讨论】:

  • 这是一个很好的答案,但如果您想要更优雅和简单的方式,请查看here
【解决方案3】:

Bingo,我发现问题了!

当我在 EditText 上使用 android:cursorVisible="false" 时,start 和 dstart 参数不匹配。

对我来说,start 参数始终为 0,但 dstart 参数也始终为 0,所以只要我使用 .replaceAll() 就可以了。这与this post 所说的相反,所以我不太明白为什么,但至少我可以构建现在可以使用的东西!

【讨论】:

【解决方案4】:

我们遇到了类似的问题,我相信解决方案[0] 也适合您。我们的要求是实现一个去除富文本输入的 EditText。例如,如果用户将粗体文本复制到剪贴板并将其粘贴到 EditText 中,则 EditText 应删除粗体强调样式并仅保留纯文本。

解决方案类如下所示:

public class PlainEditText extends EditText {
    public PlainEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        addFilter(this, new PlainTextInputFilter());
    }

    private void addFilter(TextView textView, InputFilter filter) {
        InputFilter[] filters = textView.getFilters();
        InputFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1);
        newFilters[filters.length] = filter;
        textView.setFilters(newFilters);
    }

    private static class PlainTextInputFilter implements InputFilter {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
                                   int dstart, int dend) {
            return stripRichText(source, start, end);
        }

        private CharSequence stripRichText(CharSequence str, int start, int end) {
            // ...
        }
    }
}

我们最初对 stripRichText() 的实现很简单:

// -- BROKEN. DO NOT USE --
String plainText = str.subSequence(start, end).toString();
return plainText;

Java 基 String 类不保留任何样式信息,因此将 CharSequence 接口转换为具体的 String 只会复制纯文本。

我们没有意识到的是,一些 Android 软键盘会添加并依赖临时组合提示来解决拼写错误和其他问题。该问题通过删除提示以及以意想不到的方式重复字符(通常使整个 EditText 字段的输入加倍)来表现出来。 InputFilter.filter() 的文档[1] 以这种方式传达要求:

 * Note: If <var>source</var> is an instance of {@link Spanned} or
 * {@link Spannable}, the span objects in the <var>source</var> should be 
 * copied into the filtered result (i.e. the non-null return value). 

我认为正确的解决方案是保留临时跨度:

   /** Strips all rich text except spans used to provide compositional hints. */
    private CharSequence stripRichText(CharSequence str, int start, int end) {
        String plainText = str.subSequence(start, end).toString();
        SpannableString ret = new SpannableString(plainText);
        if (str instanceof Spanned) {
            List<Object> keyboardHintSpans = getComposingSpans((Spanned) str, start, end);
            copySpans((Spanned) str, ret, keyboardHintSpans);
        }
        return ret;
    }

    /**
     * @return Temporary spans, often applied by the keyboard to provide hints such as typos.
     *
     * @see {@link android.view.inputmethod.BaseInputConnection#removeComposingSpans}
     * @see {@link android.inputmethod.latin.inputlogic.InputLogic#setComposingTextInternalWithBackgroundColor}
     */
    @NonNull private List<Object> getComposingSpans(@NonNull Spanned spanned,
                                                    int start,
                                                    int end) {
        // TODO: replace with Apache CollectionUtils.filter().
        List<Object> ret = new ArrayList<>();
        for (Object span : getSpans(spanned, start, end)) {
            if (isComposingSpan(spanned, span)) {
                ret.add(span);
            }
        }
        return ret;
    }

    private Object[] getSpans(@NonNull Spanned spanned, int start, int end) {
        Class<Object> anyType = Object.class;
        return spanned.getSpans(start, end, anyType);
    }

    private boolean isComposingSpan(@NonNull Spanned spanned, Object span) {
        return isFlaggedSpan(spanned, span, Spanned.SPAN_COMPOSING);
    }

    private boolean isFlaggedSpan(@NonNull Spanned spanned, Object span, int flags) {
        return (spanned.getSpanFlags(span) & flags) == flags;
    }

    /**
     * Apply only the spans from src to dst specific by spans.
     *
     * @see {@link android.text.TextUtils#copySpansFrom}
     */
    public static void copySpans(@NonNull Spanned src,
                                 @NonNull Spannable dst,
                                 @NonNull Collection<Object> spans) {
        for (Object span : spans) {
            int start = src.getSpanStart(span);
            int end = src.getSpanEnd(span);
            int flags = src.getSpanFlags(span);
            dst.setSpan(span, start, end, flags);
        }
    }

[0] 此处提供的实际实现:https://github.com/wikimedia/apps-android-wikipedia/blob/e9ddd8854ff15cde791a2e6fb7754a5450d6f7cf/app/src/main/java/org/wikipedia/richtext/RichTextUtil.java

[1]https://android.googlesource.com/platform/frameworks/base/+/029942f77d05ed3d20256403652b220c83dad6e1/core/java/android/text/InputFilter.java#37

【讨论】:

  • @S-Sh,我已经修复了腐烂的实现链接并添加了缺少的copySpans 函数。注意:您的里程可能会有所不同。此代码现已过时,可能相关也可能不相关。
【解决方案5】:

我只想将我的解决方案添加到问题中(尽可能晚)。我发现如果你添加

    yourEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);

然后退格问题停止

【讨论】:

  • 只有这个有帮助:editText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
猜你喜欢
  • 2013-09-02
  • 2014-10-19
  • 1970-01-01
  • 1970-01-01
  • 2016-02-26
  • 1970-01-01
  • 1970-01-01
  • 2013-09-14
  • 1970-01-01
相关资源
最近更新 更多