Как я могу изменить текст EditText без запуска Text Watcher?
У меня есть EditText
поле с наблюдателем текста клиента на нем. В куске кода мне нужно изменить значение в EditText, которое я делаю, используя .setText("whatever")
,
Проблема в том, как только я сделаю это изменение afterTextChanged
вызывается метод, который создал бесконечный цикл. Как я могу изменить текст без его срабатывания после TextChanged?
Мне нужен текст в методе afterTextChanged, поэтому не рекомендуется удалять TextWatcher
,
19 ответов
Вы можете отменить регистрацию наблюдателя, а затем перерегистрировать его.
В качестве альтернативы вы можете установить флаг, чтобы ваш наблюдатель знал, когда вы только что изменили текст самостоятельно (и поэтому должен игнорировать его).
Вы можете проверить, какое представление в настоящее время имеет фокус, чтобы различать события, инициируемые пользователем и программой.
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
}
}
//...
});
Изменить: как правильно упомянул LairdPleng, это не работает, если myEditText
уже имеет фокус, и вы программно измените текст. Итак, перед звонком myEditText.setText(...)
ты должен позвонить myEditText.clearFocus()
как сказал Chack, что решает и эту проблему.
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");
Легкий трюк для исправления... пока ваша логика для получения нового значения для редактирования текста идемпотентна (что, вероятно, будет, но просто скажем). В вашем методе слушателя изменяйте текст редактирования только в том случае, если текущее значение отличается от того, которое вы в последний раз изменяли.
например,
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);
}
}
};
Вы можете использовать синтаксис Kotlin DSL, чтобы иметь общее решение для этого:
fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
this.removeTextChangedListener(textWatcher)
codeBlock()
this.addTextChangedListener(textWatcher)
}
А внутри вашего TextWatcher вы можете использовать его как:
editText.applyWithDisabledTextWatcher(this) {
text = formField.name
}
Проблема может быть легко решена с помощью 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
}
}
Я использую этот способ:
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 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) { }
});
Привет, если вам нужно сосредоточиться на EditText
изменить текст вы можете запросить фокус. Это сработало для меня:
if (getCurrentFocus() == editText) {
editText.clearFocus();
editText.setText("...");
editText.requestFocus();
}
Просто сделай это так
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
}
}
})
таким образом, он не имеет бесконечного цикла
Вот удобный класс, который предоставляет более простой интерфейс, чем 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
Попробуйте эту логику: я хотел установить текст (""), не переходя в бесконечный цикл, и этот код работает для меня. Я надеюсь, что вы можете изменить это, чтобы соответствовать вашим требованиям
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
class ControlledTextWatcher(
private val parent: TextView,
private val onChange: ((text: CharSequence?, start: Int, before: Int, count: Int) -> Unit)?,
private val beforeChange: ((text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null,
private val afterChange: ((editable: Editable?) -> Unit)? = null
) : TextWatcher {
init {
parent.addTextChangedListener(this)
}
private var enabled = true
var text: String?
get() = parent.value
set(value) {
this.enabled = false
parent.text = value
this.enabled = true
}
var res: Int
get() = throw RuntimeException("String resource cannot be retrieved after being set")
set(value) {
parent.text = parent.context.getString(value)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
if (enabled)
beforeChange?.invoke(s, start, count, after)
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (enabled)
onChange?.invoke(s, start, before, count)
}
override fun afterTextChanged(s: Editable?) {
if (enabled)
afterChange?.invoke(s)
}
fun detach() {
parent.removeTextChangedListener(this)
}
}
и я использую его в основном с привязками просмотра, например
class TestActivity: AppCompatActivity () {
class TestActivity : AppCompatActivity() {
private lateinit var binding: ActivityTestBinding
private val edit by lazy { ControlledTextWatcher(binding.text, this::textChanged }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestBinding.inflate(layoutInflater)
setContentView(binding.root)
}
поэтому, когда я хочу внести изменения в фактическое, я использую
text
или
res
атрибут подобного так:
edit.text = "hello world" //this does not trigger the text watcher
но когда пользователь изменяет, он запускает
к сожалению, с этим решением, если вы хотите изменить другие параметры, вам либо нужно получить исходный
EditText
через привязки или скопируйте эти функции в
ControlledTextWatcher
также вы должны быть осторожны при внесении изменений в
afterChange
потому что изменение опубликовано в
TextView
так что вы можете получить бесконечный цикл
Очень просто, установите текст с помощью этого метода
void updateText(EditText et, String text) {
if (!et.getText().toString().equals(text))
et.setText(text);
}
Вы должны убедиться, что ваша реализация текстовых изменений стабильна и не меняет текст, если никаких изменений не требуется. Обычно это будет любой контент, который уже был когда-то через наблюдателя.
Наиболее распространенной ошибкой является установка нового текста в связанном EditText или Editable, даже если текст фактически не изменялся.
Кроме того, если вы вносите свои изменения в Editable вместо какого-то определенного представления, вы можете легко повторно использовать ваш наблюдатель, а также вы можете протестировать его изолированно с помощью некоторых модульных тестов, чтобы убедиться, что он имеет желаемый результат.
Поскольку Editable является интерфейсом, вы можете даже использовать его фиктивную реализацию, которая выдает исключение RuntimeException, если вызывается какой-либо из его методов, пытающихся изменить его содержимое при тестировании содержимого, которое должно быть стабильным.
Мой способ сделать это:
В сегменте записи
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;
....
Все равно работает.