EditText и InputFilter вызывают повторяющийся текст
Я пытаюсь реализовать EditText, который ограничивает ввод только альфа-символами [A-Za-z].
Я начал с метода InputFilter из этого сообщения. Когда я набираю "%", текст исчезает, и если я нахожусь в обратном пространстве, текст "a". Я пробовал другие варианты функции фильтра, такие как использование регулярного выражения, чтобы соответствовать только [A-Za-z], и иногда вижу сумасшедшее поведение, такое как повторяющиеся символы, я набираю "a", затем "b" и получаю "aab", затем введите "c" и получите "aabaabc", затем нажмите "backspace" и получите "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 } );
Я полностью зациклен на этом, поэтому любая помощь здесь будет очень признательна.
EDIT:
Хорошо, я довольно много экспериментировал с InputFilter и сделал некоторые выводы, хотя и не решил проблему. См. Комментарии в моем коде ниже. Я сейчас попробую решение 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 } );
Ответы
Ответ 1
Бинго, я нашел проблему!
Когда я использую android: cursorVisible = "false" в EditText, параметры start и dstart не совпадают правильно.
Параметр start всегда всегда равен 0 для меня, но параметр dstart также всегда равен 0, поэтому он работает до тех пор, пока я использую .replaceAll(). Это противоречит тому, что этот пост говорит, поэтому я не совсем понимаю, почему, но по крайней мере я могу создать что-то, что работает сейчас!
Ответ 2
Используйте следующий код:
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,"");
}
}
}
});
Ответ 3
У нас была аналогичная проблема, и я считаю, что решение [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;
Базовый класс String Java не сохраняет никакой информации о стиле, поэтому преобразование интерфейса CharSequence в конкретный String копирует только обычный текст.
Мы не понимали, что некоторые мягкие клавиатуры Android добавляют и зависят от временных композиционных подсказок для опечаток и других вещей. Проблема проявляется в устранении подсказок, а также повторении символов неожиданным образом (обычно удваивая весь ввод поля EditText). Документация [1] для InputFilter.filter() сообщает это требование следующим образом:
* 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;
}
[0] Фактическая реализация доступна здесь: 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
Ответ 4
Я просто хотел бы добавить свое решение проблемы (пока оно есть). Я обнаружил, что если вы добавите
yourEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Тогда проблемы с обратным пространством останавливаются
Ответ 5
Исправление для повторения текста, работа со всеми версиями 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)});