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
.
You could unregister the watcher, and then re-register it.
Alternatively, you could set a flag so that your watcher knows when you have just changed the text yourself (and therefore should ignore it).
You can check which View currently has the focus to distinguish between user and program triggered events.
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 correctly mentioned, this does not work if the myEditText
already has the focus and you programmatically change the text. So, before calling myEditText.setText(...)
you should call myEditText.clearFocus()
as Chack said, which solves this problem as well.
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);
}
}
Usage:
et_text.addTextChangedListener(new MyTextWatcher(et_text));
You may feel a little bit lag when entering text rapidly if you are using editText.setText() instead of editable.replace().
Easy trick to fix ... as long a your logic to derive the new edit text value is idempotent (which it probably would be, but just saying). In your listener method, only modify the edit text if the current value is different than the last time you modified the value.
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);
}
}
};
Hi if you need to stay focused on EditText
change text you could request focus. This worked for me:
if (getCurrentFocus() == editText) {
editText.clearFocus();
editText.setText("...");
editText.requestFocus();
}
I use that way:
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);
try this logic: I wanted to setText("") without going to infinite loop and this code works for me. I hope you can modify this to fit your requirement
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
}
});
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);
}
});
}
}
Set listeners only using setOnTextChangeListener() and set text only using setNewText (I wanted to override setText(), but it is final)
I've created an abstract class which mitigates the cyclic issue of when a modification to the EditText is made via a 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);
}
Here's a handy class that provides a simpler interface than TextWatcher for the normal case of wanting to see changes as they occur. It also allows for ignoring the next change as the OP requested.
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());
}
}
}
}
Use it like this:
EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);
Whenever you want to modify the contents of editText
without causing a cascade of recursive edits, do this:
listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener
You can use Kotlin DSL syntax to have the generic solution for this:
fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
this.removeTextChangedListener(textWatcher)
codeBlock()
this.addTextChangedListener(textWatcher)
}
And inside your TextWatcher, you can use it as:
editText.applyWithDisabledTextWatcher(this) {
text = formField.name
}
This works good for me
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) { }
});
The problem can be easily solved using tag
filed and you don't even have to deal with editText's focus.
Setting the text and the tag programmatically
editText.tag = "dummyTag"
editText.setText("whatever")
editText.tag = null
Checking for the tag
in onTextChanged
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (editText.tag == null) {
// your code
}
}
You should ensure your implementation of text changes is stable and does not change the text if no change is needed. Normally that would be any content that's already been through the watcher once.
The most common mistake is to set a new text in the associated EditText or the Editable even though the text was not actually changes.
On top of that, if you make your changes to the Editable instead of some specific View, you can easily resuse your watcher, and also you can test it in isolation with some unit tests to ensure it has the outcome you want.
Since Editable is an interface you could even use a dummy implementation of it that throws a RuntimeException if any of its methods are called that try to change its contents, when testing content that should be stable.
来源:https://stackoverflow.com/questions/9385081/how-can-i-change-the-edittext-text-without-triggering-the-text-watcher