Как я могу изменить текст EditText без запуска наблюдателя текста?

у меня есть EditText поле с наблюдателем текста клиента на нем. В фрагменте кода мне нужно изменить значение в EditText, которое я использую .setText("whatever").

проблема в том, как только я сделаю это изменение afterTextChanged вызывается метод, который создал бесконечный цикл. Как я могу изменить текст, без его запуска afterTextChanged?

мне нужен текст в методе afterTextChanged, поэтому не предлагайте удалять TextWatcher.

12 ответов


вы можете отменить регистрацию наблюдателя, а затем вновь зарегистрировать его.

кроме того, вы можете установить флаг, чтобы ваш наблюдатель знал, когда вы только что изменили текст самостоятельно (и, следовательно, должны игнорировать его).


вы можете проверить, какой вид в настоящее время имеет фокус, чтобы различать события, вызванные пользователем и программой.

EditText myEditText = (EditText) findViewById(R.id.myEditText);

myEditText.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (getCurrentFocus() == myEditText) {
            // is only executed if the EditText was directly changed by the user
        }
    }

    //...
});

Edit: As LairdPleng правильно сказали, это не работает, если myEditText уже имеет фокус, и вы программно изменяете текст. Итак, перед вызовом myEditText.setText(...) вы должны позвонить myEditText.clearFocus() as проверяем, сказал, что решает эту проблему.


легко исправить ... пока ваша логика для получения нового текстового значения редактирования является идемпотентной (что, вероятно, было бы, но просто говорю). В методе прослушивателя измените текст редактирования, только если текущее значение отличается от последнего изменения значения.

например,

TextWatcher tw = new TextWatcher() {
  private String lastValue = "";

  @Override
  public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
  }

  @Override
  public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
  }

  @Override
  public void afterTextChanged(Editable editable) {

    // Return value of getNewValue() must only depend
    // on the input and not previous state
    String newValue = getNewValue(editText.getText().toString());
    if (!newValue.equals(lastValue)) {
      lastValue = newValue;

      editText.setText(newValue);
    }
  }
};

public class MyTextWatcher implements TextWatcher {
    private EditText et;

    // Pass the EditText instance to TextWatcher by constructor
    public MyTextWatcher(EditText et) {
        this.et = et;
    }

    @Override
    public void afterTextChanged(Editable s) {
        // Unregister self before setText
        et.removeTextChangedListener(this);

        et.setText("text");

        // Re-register self after setText
        et.addTextChangedListener(this);
    }
}

использование:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

обновление:

обновить текст более плавно, замените

et.setText("text");

С

s.replace(0, s.length(), "text");

Привет, если вам нужно сосредоточиться на EditText изменить текст, вы можете запросить фокус. Это сработало для меня:

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}

попробуйте эту логику: Я хотел установить текст (""), не переходя в бесконечный цикл, и этот код работает для меня. Я надеюсь, что вы можете изменить это, чтобы соответствовать вашему требованию

        final EditText text= (EditText)findViewById(R.id.text);
        text.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) {

        }
        @Override
        public void afterTextChanged(Editable s) {
            if(s.toString().isEmpty())return;
            text.setText("");
            //your code
        }
    });

мой вариант:

public class CustomEditText extends AppCompatEditText{
    TextWatcher l;

    public CustomEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void setOnTextChangeListener(TextWatcher l) {
        try {
            removeTextChangedListener(this.l);
        } catch (Throwable e) {}
        addTextChangedListener(l);
        this.l = l;
    }

    public void setNewText(CharSequence s) {
        final TextWatcher l = this.l;
        setOnTextChangeListener(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) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        setText(s);
        post(new Runnable() {
            @Override
            public void run() {
                setOnTextChangeListener(l);
            }
        });
    }


}

установить прослушиватели только с помощью setOnTextChangeListener () и установить текст только с помощью setNewText (я хотел переопределить setText (), но это окончательно)


бесконечный цикл может возникнуть при настройке текста из текстового измененного прослушивателя. Если это так, возможно, лучше не устанавливать текст оттуда в первую очередь. Таким образом, вы решите проблему цикла, и нет причин обходить или потреблять событие.


Я создал абстрактный класс, который смягчает циклическую проблему, когда модификация EditText производится через TextWatcher.

/**
 * An extension of TextWatcher which stops further callbacks being called as a result of a change
 * happening within the callbacks themselves.
 */
public abstract class EditableTextWatcher implements TextWatcher {

    private boolean editing;

    @Override
    public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (editing)
            return;

        editing = true;
        try {
            beforeTextChange(s, start, count, after);
        } finally {
            editing = false;
        }
    }

    abstract void beforeTextChange(CharSequence s, int start, int count, int after);

    @Override
    public final void onTextChanged(CharSequence s, int start, int before, int count) {
    if (editing)
        return;

        editing = true;
        try {
            onTextChange(s, start, before, count);
        } finally {
            editing = false;
        }
    }

    abstract void onTextChange(CharSequence s, int start, int before, int count);

    @Override
    public final void afterTextChanged(Editable s) {
        if (editing)
            return;

        editing = true;
        try {
            afterTextChange(s);
        } finally {
            editing = false;
        }
    }    

    public boolean isEditing() {
        return editing;
    }

    abstract void afterTextChange(Editable s);
}

вот удобный класс, который обеспечивает более простой интерфейс, чем TextWatcher для обычного случая, когда вы хотите видеть изменения по мере их возникновения. Он также позволяет игнорировать следующее изменение по запросу OP.

public class EditTexts {
    public final static class EditTextChangeListener implements TextWatcher {
        private final Consumer<String> onEditTextChanged;
        private boolean ignoreNextChange = false;
        public EditTextChangeListener(Consumer<String> onEditTextChanged){
            this.onEditTextChanged = onEditTextChanged;
        }
        public void ignoreNextChange(){
            ignoreNextChange = true;
        }
        @Override public void beforeTextChanged(CharSequence __, int ___, int ____, int _____) { }
        @Override public void onTextChanged(CharSequence __, int ___, int ____, int _____) { }
        @Override public void afterTextChanged(Editable s) {
            if (ignoreNextChange){
                ignoreNextChange = false;
            } else {
                onEditTextChanged.accept(s.toString());
            }
        }
    }
}

используйте его так:

EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);

всякий раз, когда вы хотите изменить содержание editText не вызывая каскада рекурсивных изменений, сделайте следующее:

listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener

Я использую таким образом:

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

            @Override
            public void afterTextChanged(Editable s) {
                if (mEditText.isFocused()) { //<-- check if is focused 
                    mEditText.setTag(true);
                }
            }
        });

и каждый раз, когда вам нужно изменить текст программно, сначала очистите фокус

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);

вы должны убедиться, что ваша реализация изменений текста стабильна и не изменяет текст, если никаких изменений не требуется. Обычно это будет любой контент, который уже прошел через наблюдателя один раз.

наиболее распространенной ошибкой является установка нового текста в связанный EditText или редактируемый, даже если текст на самом деле не был изменен.

кроме того, если вы внесете изменения в редактируемый вместо определенного представления, вы можете легко повторно использовать наблюдатель, а также вы можете протестировать его изолированно с помощью некоторых модульных тестов, чтобы убедиться, что он имеет желаемый результат.

поскольку редактируемый-это интерфейс, вы можете даже использовать его фиктивную реализацию, которая вызывает исключение RuntimeException, если какой-либо из его методов вызван, который пытается изменить его содержимое при тестировании содержимого, которое должно быть стабильным.