How can I change the EditText text without triggering the Text Watcher?

I have an EditText field with a Customer Text Watcher on it. In a piece of code I need to change the value in the EditText which I do using .setText("whatever").

The problem is as soon as I make that change the afterTextChanged method gets called which created an infinite loop. How can I change the text without it triggering afterTextChanged?

I need the text in the afterTextChanged method so don't suggest removing the TextWatcher.

77651 次浏览

你可以取消观察者的注册,然后重新注册。

或者,您可以设置一个标志,以便您的观察者知道什么时候您刚刚自己更改了文本(因此应该忽略它)。

修复简单的技巧... ... 只要导出新编辑文本值的逻辑是幂等的(它可能是,但只是说)。在侦听器方法中,只有当当前值与上次修改该值时不同时,才修改编辑文本。

例如:

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

您应该确保文本更改的实现是稳定的,并且如果不需要更改,则不会更改文本。一般来说,那是任何已经通过观察者一次的内容。

最常见的错误是在关联的 EditText 或 Editable 中设置新文本,即使文本实际上没有更改。

最重要的是,如果你改变了可编辑视图而不是某个特定的视图,你可以很容易地重新使用你的监视器,你也可以用一些单元测试来单独测试它,以确保它有你想要的结果。

由于 Editable 是一个接口,您甚至可以使用它的一个虚拟实现,当测试应该是稳定的内容时,如果调用它的任何尝试更改其内容的方法,该实现都会抛出 RuntimeException。

My variant:

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 () ,但它是 final)

试试这个逻辑: 我想在不进行无限循环的情况下设置文本(“”) ,这段代码对我很有用。我希望你能修改这个来满足你的要求

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

长话短说

您可以检查哪个 View 当前具有区分用户触发事件和程序触发事件的焦点。

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

For Kotlin:

Since Kotlin supports extension functions your utility function could look like this:

fun EditText.updateText(text: String) {
val focussed = hasFocus()
if (focussed) {
clearFocus()
}
setText(text)
if (focussed) {
requestFocus()
}
}

Java:

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

如果您使用的是 SetText ()而不是 Place (),那么在快速输入文本时您可能会感到有点滞后。

我已经创建了一个抽象类,它可以缓解何时通过 TextWatcher 修改 EditText 的循环问题。

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

如果你需要把注意力集中在 EditText的修改文本上,你可以要求集中:

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

这里有一个方便的类,它提供了一个比 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);
}
}
});

And every time you need to change text programatically, first clear the focus

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

您可以使用 Kotlin DSL 语法来获得这个问题的通用解决方案:

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

在你的 TextWatcher 中,你可以把它用作:

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

这对我有好处

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

这个问题可以很容易地解决使用 tag文件,你甚至不必处理的编辑文本的焦点。

以编程方式设置文本和标记

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

检查 onTextChanged 中的 tag

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

我做事的方式:

在写入段中

        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;


....

反正有用。

非常简单,用这种方法设置文本

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

这样做很简单

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

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

而且我主要用它来处理视图绑定,就像这样

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

因此,当我希望对实际的 EditText进行修改时,我使用 ControlledTextWatchertextres属性,如下所示:

edit.text = "hello world" //this does not trigger the text watcher

but when the user alters the EditText it will trigger

unfortunatelly with this solution if you want to alter other parameters of the EditText, you either have to get the original EditText through bindings or copy those functions to the ControlledTextWatcher

你也必须小心在 afterChange的变化,因为变化是张贴到 TextView,所以你可能会以一个无止境的循环结束