Как я могу изменить текст EditText, не вызывая Text Watcher?

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

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

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


person user1143767    schedule 21.02.2012    source источник


Ответы (17)


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

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

person CasualT    schedule 21.02.2012

Короткий ответ

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

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

myEditText.addTextChangedListener(new TextWatcher() {

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

    //...
});

Длинный ответ

В дополнение к короткому ответу: в случае, если myEditText уже имеет фокус, когда вы программно меняете текст, вы должны вызвать clearFocus(), затем вы вызываете setText(...) и после того, как вы повторно запрашиваете фокус. Было бы неплохо поместить это в функцию полезности:

void updateText(EditText editText, String text) {
    boolean focussed = editText.hasFocus();
    if (focussed) {
        editText.clearFocus();
    }
    editText.setText(text);
    if (focussed) {
        editText.requestFocus();
    }
}

Для Котлина:

Поскольку Kotlin поддерживает функции расширения, ваша служебная функция может выглядеть так:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
person Willi Mentzel    schedule 15.10.2015
comment
Не идеально. Что делать, если я хочу установить текст() без прослушивателя триггера, даже если текст редактирования имеет фокус? - person Jaya Prakash; 02.03.2020

Ява:

public class MyTextWatcher implements TextWatcher {
    private EditText editText;

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

    @Override
    public void afterTextChanged(Editable e) {
        // Unregister self before update
        editText.removeTextChangedListener(this);

        // The trick to update text smoothly.
        e.replace(0, e.length(), e.toString());

        // Re-register self after update
        editText.addTextChangedListener(this);
    }

    ...
}

Котлин:

class MyTextWatcher(private val editText: EditText) : TextWatcher {

  override fun afterTextChanged(e: Editable) {
    editText.removeTextChangedListener(this)
    e.replace(0, e.length, e.toString())
    editText.addTextChangedListener(this)
  }

  ...
}

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

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Вы можете почувствовать небольшую задержку при быстром вводе текста, если используете editText.setText() вместо editable.replace().

person Chan Chun Him    schedule 30.12.2015
comment
Почему за это проголосовали? Это идеальное решение моей проблемы, тогда как другие не работали. Я добавлю, что нет необходимости создавать подкласс TextWatcher, чтобы это решение работало. Спасибо, Чан Чун Хим. - person Michael Fulton; 08.07.2016
comment
Ваша идея по отмене регистрации и перерегистрации TextWatcher отлично работает. Однако если et.setText() работает, то s.replace(0, s.length(), ) — нет. - person stevehs17; 28.07.2017
comment
@ stevehs17, я действительно не знаю, как ваш код, но использование было обновлено очень подробно, попробуйте. - person Chan Chun Him; 28.07.2017

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

e.g.,

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);
    }
  }
};
person Jeffrey Blattman    schedule 29.05.2013
comment
Однако это все равно приведет к тому, что метод будет вызываться дважды, не так ли? - person Trevor Hart; 29.04.2019
comment
Да, именно поэтому я заявил, что он должен быть идемпотентным (должен был сделать это более ясным) ... не идеальным, но вы должны действительно жестко оптимизировать, если вас беспокоят накладные расходы на вызов одного метода, который не работает. Если это так, вы можете реорганизовать код, чтобы удалить прослушиватель до того, как он вызовет setText(), и повторно добавить его после. - person Jeffrey Blattman; 30.04.2019

Вы можете использовать синтаксис Kotlin DSL, чтобы получить общее решение для этого:

fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
    this.removeTextChangedListener(textWatcher)
    codeBlock()
    this.addTextChangedListener(textWatcher)
}

И внутри вашего TextWatcher вы можете использовать его как:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
person Alex Misiulia    schedule 10.05.2019
comment
Гораздо чище. Kotlin DSL великолепен - person Anjal Saneen; 23.08.2019
comment
Это оно. Спасибо. - person ACAkgul; 29.08.2020

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

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);
person Arthur Melo    schedule 07.08.2018

Это работает хорошо для меня

EditText inputFileName; // = (EditText)findViewbyId(R.id...)
inputFileName.addTextChangedListener(new TextWatcher() {
        public void afterTextChanged(Editable s) {

            //unregistering for event in order to prevent infinity loop
            inputFileName.removeTextChangedListener(this);

            //changing input's text
            String regex = "[^a-z0-9A-Z\\s_\\-]";
            String fileName = s.toString();
            fileName = fileName.replaceAll(regex, "");
            s.replace(0, s.length(), fileName); //here is setting new text

            Log.d("tag", "----> FINAL FILE NAME: " + fileName);

            //registering back for text changes
            inputFileName.addTextChangedListener(this);
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

        public void onTextChanged(CharSequence s, int start, int before, int count) { }
    });
person user3361395    schedule 19.07.2019

Проблема может быть легко решена с помощью поля tag, и вам даже не придется иметь дело с фокусом editText.

Настройка текста и тега программно

editText.tag = "dummyTag"
editText.setText("whatever")
editText.tag = null

Проверка tag в onTextChanged

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    if (editText.tag == null) {
       // your code
    }
}
person Levon Petrosyan    schedule 24.07.2019

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

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}
person Milos Simic Simo    schedule 02.08.2017

Просто сделай это так

editText.addTextChangedListener(object : TextWatcher {

        private var isEditing = false

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {


        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

        }

        override fun afterTextChanged(s: Editable?) {
            if(!isEditing){
                isEditing = true
                editText.setText("Hello World!")
                isEditing = false
            }

        }
    })

таким образом, он не накапливается в бесконечном цикле

person Arian Shahpasand    schedule 28.10.2020

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

        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
        }
    });
person ShAkKiR    schedule 31.08.2015

Вот удобный класс, который предоставляет более простой интерфейс, чем 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
person JohnnyLambada    schedule 27.03.2018

Мой вариант:

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(), но это окончательно)

person kandi    schedule 19.08.2015

Я создал абстрактный класс, который смягчает циклическую проблему, когда модификация 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);
}
person Eurig Jones    schedule 21.03.2017

Очень просто, установите текст с помощью этого метода

void updateText(EditText et, String text) {
   if (!et.getText().toString().equals(text))
       et.setText(text);
}
person utrucceh    schedule 13.05.2020

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

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

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

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

person thoutbeckers    schedule 19.06.2014

Мой способ сделать это:

В сегменте записи

        EditText e_q;

        e_q = (EditText) parentView.findViewWithTag("Bla" + i);

        int id=e_q.getId();
        e_q.setId(-1);
        e_q.setText("abcd...");
        e_q.setId(id);

Слушатель

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

        int id = view.getId();
        if(id==-1)return;

        ....

Работает в любом случае.

person Paolo    schedule 11.02.2020