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


104

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

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

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

Ответы:


70

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

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


Мне твоего намека достаточно. На самом деле я регистрировал слушателя в onResume, но не отменял регистрацию в onPause (), поэтому он звонил несколько раз.
Smeet

Может кто-нибудь объяснить это? Технически, я новичок в Android, нужны подробности, пожалуйста, спасибо.
Budi Mulyo

117

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

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

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

во фрагменте getActivity().getCurrentFocus()и котлинеactivity?.currentFocus
Мохаммад Реза Хахани

@MohammadRezaKhahani Привет! тебе это не нужно. Вы можете позвонить hasFocus()прямо по адресу EditText.
Вилли Ментцель

Не идеально. Что, если я хочу использовать setText () без прослушивателя триггера, даже если у edittext есть фокус?
Джая Пракаш,

31
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 update
        et.removeTextChangedListener(this);

        // The trick to update text smoothly.
        s.replace(0, s.length(), "text");

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

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

et_text.addTextChangedListener(new MyTextWatcher(et_text));

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


Почему это было отклонено? Это идеальное решение моей проблемы, тогда как другие не сработали. Я добавлю, что нет необходимости создавать подкласс TextWatcher, чтобы это решение работало. Спасибо Чан Чун Ему.
Майкл Фултон

Ваша идея отменить регистрацию и перерегистрировать TextWatcher отлично работает. Однако, хотя et.setText ("") работает, s.replace (0, s.length (), "") нет.
stevehs17

@ stevehs17, я действительно не знаю, как ваш код, но использование было обновлено очень подробно, попробуйте.
Чан Чун Хим

15

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

например,

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

1
Это все равно приведет к тому, что метод будет вызываться дважды, правда?
Trevor Hart

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

7

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

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

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

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}

Намного чище. Kotlin DSL потрясающий
Anjal Saneen

Это оно. Спасибо.
ACAkgul

6

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

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

4

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

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

3

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

Программная установка текста и тега

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

Проверка tagonTextChanged

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

3

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

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

1

попробуйте такую ​​логику: я хотел 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
        }
    });

1

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

0

Мой вариант:

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


0

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

0

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

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

-2

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

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

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

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


-2

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

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

        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;

        ....

Все равно работает.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.