Pytanie EditText i InputFilter powodują powtarzanie tekstu


Próbuję zaimplementować EditText, który ogranicza wejście tylko do alfa-znaków [A-Za-z].

Zacząłem od metody InputFilter od ten post. Kiedy wpisuję "a%" tekst znika, wtedy gdy trafiam w backspace, tekst to "a". Próbowałem innych odmian funkcji filtru, takich jak użycie wyrażenia regularnego tylko do dopasowania [A-Za-z], a czasami widzę szalone zachowanie, takie jak powtarzanie znaków, wpiszę "a", a następnie "b" i otrzymam "aab", a następnie wpisz "c" i uzyskaj "aabaabc", a następnie naciśnij backspace i uzyskaj "aabaabcaabaabc"!

Oto kod, nad którym pracuję do tej pory z różnymi podejściami, które wypróbowałem.

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

Jestem całkowicie zaskoczony tym, więc jakakolwiek pomoc tutaj będzie bardzo ceniona.

EDYTOWAĆ: Ok, zrobiłem sporo eksperymentów z InputFilter i wyciągnąłem pewne wnioski, aczkolwiek nie było rozwiązania problemu. Zobacz komentarze w moim kodzie poniżej. Zamierzam teraz wypróbować rozwiązanie Imrana Rany.

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

12
2018-05-29 01:15


pochodzenie


Twój kod za pomocą .replaceAll() jest prawie poprawne. Powtarzające się znaki są powodowane przez tworzenie ciągu znaków z pełnym ciągiem wejściowym String data = source.toString(), podczas gdy powinieneś pracować nad podzestawem Android pyta o, jak w String data = source.toString().substring(start,end) - chr
Wygląda na to, że mam problem z tym, że start to zawsze 0, gdy wprowadzam 1 znak na raz. Stąd dane String = source.toString (). Substring (start, end); zawsze daje mi pełny tekst, a nie tylko to, co się zmieniło. - Rooster242
... Tak, właśnie wypróbowałem próbkę ten post i start jest zawsze 0, więc otrzymuję z nim powtórzone znaki. - Rooster242


Odpowiedzi:


Użyj następującego kodu:

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

    }
   });

Jeśli chcesz, aby poprawny tekst pozostał w 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,"");               
             }
         }
    }
   });

4
2018-05-29 08:02



Działa to doskonale dla mnie, daj mi znać, jeśli ci to pomoże :) - Imran Rana
To prawie dla mnie działa. Chcę, aby poprawny tekst pozostał w EditText. Więc jeśli wprowadzę "a", a następnie "b", to "^" chcę nadal widzieć "ab" w EditText. Oczywiście jeśli wywołasz setText z dowolnym, ale pustym łańcuchem, to on onTextChanged wywołuje w nieskończoność. - Rooster242
@ Rooster242 zobacz moją edycję, jeśli chcesz zachować poprawny tekst Edytować tekst po prostu dodaj powyższy kod do afterTextChanged () metoda i usuń poprzedni kod z onTextChanged () metoda - Imran Rana
Ah, dzięki Imran, działa świetnie. Oba podejścia działają właściwie (InputFilter i TextWatcher) Postanowiłem użyć TextWatchera, ponieważ jest trochę łatwiej zobaczyć, co się dzieje, imho i nie potrzebuję używać czarnej listy w replaceAll (). Na tym etapie zaznaczam własną odpowiedź, ponieważ to powtarzające się znaki wywoływane przez android: cursorVisible = "false" naprawdę mnie podnieciły. Stworzyłem dla niego raport o błędzie, Numer 32475. - Rooster242
Nie powiodło się z InputFilter, podczas gdy TextWatcher wspaniale współpracuje z IME - Tertium


Bingo, znalazłem problem!

Kiedy używam android: cursorVisible = "false" w EditText, parametry start i dstart nie pasują do siebie poprawnie.

Parametr start jest dla mnie zawsze 0, ale parametr dstart jest zawsze równy 0, więc działa tak długo, jak długo używam .replaceAll (). Jest to sprzeczne z tym ten post mówi, więc nie do końca rozumiem, dlaczego, ale przynajmniej mogę zbudować coś, co działa teraz!


1
2018-05-29 23:36



Widzieć code.google.com/p/android/issues/detail?id=28074 w celu uzyskania dodatkowych informacji na temat tego błędu. - ditkin
Więc czym jest Rozwiązanie, w obliczu tego samego problemu Pomóżcie mi - sam_k


Mieliśmy podobny problem i uważam, że rozwiązanie [0] również będzie dla ciebie skuteczne. Naszym wymaganiem było wdrożenie EditTextu, który pozbawił wejścia tekstu sformatowanego. Na przykład, jeśli użytkownik skopiował pogrubiony tekst do schowka i wkleił go do EditText, EditText powinien usunąć styl pogrubienia i zachować tylko zwykły tekst.

Klasa rozwiązania wygląda mniej więcej tak:

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) {
            // ...
        }
    }
}

Nasza oryginalna implementacja dla stripRichText () była prosta:

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

Podstawowa klasa String języka Java nie zachowuje żadnych informacji o stylizacji, dlatego przekonwertowanie interfejsu CharSequence na konkretny ciąg powoduje skopiowanie tylko zwykłego tekstu.

Nie zdawaliśmy sobie sprawy, że niektóre klawiatury z systemem Android dodają i zależą od tymczasowych wskazówek kompozycyjnych dotyczących literówek i innych rzeczy. Problem przejawia się poprzez usunięcie wskazówek oraz powtarzanie znaków w nieoczekiwany sposób (zwykle podwojenie całego pola edycji pola tekstowego). Dokumentacja [1] dla InputFilter.filter () komunikuje wymaganie w ten sposób:

 * 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). 

Uważam, że właściwym rozwiązaniem jest zachowanie tymczasowych rozpiętości:

   /** 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;
    }

[0] Rzeczywiste wdrożenie dostępne tutaj: https://git.wikimedia.org/blob/apps%2Fandroid%2Fwikipedia/e9ddd8854ff15cde791a2e6fb7754a5450d6f7cf/app%2Fsrc%2Fmain%2Fjava%2Forg%2Fwikipedia%2Frichtext%2FRichTextUtil.java

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


1
2017-10-21 21:22





Chciałbym tylko dodać moje rozwiązanie problemu (tak późno, jak to jest). Znalazłem to, jeśli dodasz

    yourEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);

Następnie problemy z backspace zostaną zatrzymane


0
2018-04-06 14:08



Pomogło to tylko: editText.setInputType (InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - Nikita Axyonov


Napraw powtarzanie tekstu, pracuj nad wszystkimi wersjami Androida:

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

Teraz możesz użyć tego filtra jak:

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

0
2017-11-29 12:46