J'ai un champ EditText
avec un observateur de texte client dessus. Dans un morceau de code, je dois changer la valeur dans le EditText que je fais en utilisant .setText("whatever")
.
Le problème est que dès que je fais ce changement, la méthode afterTextChanged
est appelée, ce qui a créé une boucle infinie. Comment puis-je changer le texte sans qu'il déclenche afterTextChanged?
J'ai besoin du texte dans la méthode afterTextChanged, donc ne suggérez pas de supprimer le TextWatcher
.
Vous pouvez annuler l'enregistrement de l'observateur, puis le réenregistrer.
Alternativement, vous pouvez définir un indicateur pour que votre observateur sache quand vous venez de modifier le texte vous-même (et doit donc l'ignorer).
Vous pouvez vérifier quelle vue a actuellement le focus pour faire la distinction entre les événements déclenchés par l'utilisateur et le programme.
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: Comme LairdPleng correctement mentionné, cela ne fonctionne pas si le myEditText
a déjà le focus et que vous modifier par programme le texte. Donc, avant d'appeler myEditText.setText(...)
, vous devez appeler myEditText.clearFocus()
comme Chack said, ce qui résout également ce problème.
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));
Vous pouvez ressentir un peu de retard lors de la saisie rapide de texte si vous utilisez editText.setText () au lieu de editable.replace ().
Astuce facile à corriger ... tant que votre logique pour dériver la nouvelle valeur de texte d'édition est idempotente (ce qu'elle serait probablement, mais juste en disant). Dans votre méthode d'écoute, modifiez le texte d'édition uniquement si la valeur actuelle est différente de la dernière fois que vous avez modifié la valeur.
par exemple.,
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);
}
}
};
J'utilise de cette façon:
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);
}
}
});
Et chaque fois que vous devez modifier le texte par programme, effacez d'abord le focus
mEditText.clearFocus();
mEditText.setText(lastAddress.complement);
Salut, si vous devez rester concentré sur EditText
changer le texte, vous pouvez demander le focus. Cela a fonctionné pour moi:
if (getCurrentFocus() == editText) {
editText.clearFocus();
editText.setText("...");
editText.requestFocus();
}
Vous pouvez utiliser la syntaxe DSL de Kotlin pour avoir la solution générique pour cela:
fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
this.removeTextChangedListener(textWatcher)
codeBlock()
this.addTextChangedListener(textWatcher)
}
Et à l'intérieur de votre TextWatcher, vous pouvez l'utiliser comme:
editText.applyWithDisabledTextWatcher(this) {
text = formField.name
}
essayez cette logique: je voulais setText ("") sans aller à la boucle infinie et ce code fonctionne pour moi. J'espère que vous pourrez modifier cela pour répondre à vos besoins
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
}
});
Cela fonctionne bien pour moi
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) { }
});
Voici une classe pratique qui fournit une interface plus simple que TextWatcher pour le cas normal de vouloir voir les changements lorsqu'ils se produisent. Il permet également d'ignorer la prochaine modification lorsque l'OP le demande.
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());
}
}
}
}
Utilisez-le comme ceci:
EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);
Chaque fois que vous souhaitez modifier le contenu de editText
sans provoquer une cascade de modifications récursives, procédez comme suit:
listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener
Le problème peut être facilement résolu en utilisant tag
déposé et vous n'avez même pas à gérer le focus de editText.
Définition du texte et de la balise par programme
editText.tag = "dummyTag"
editText.setText("whatever")
editText.tag = null
Vérification de la tag
dans onTextChanged
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (editText.tag == null) {
// your code
}
}
Ma variante:
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);
}
});
}
}
Définissez les écouteurs uniquement à l'aide de setOnTextChangeListener () et définissez le texte uniquement à l'aide de setNewText (je voulais remplacer setText (), mais c'est définitif)
J'ai créé une classe abstraite qui atténue le problème cyclique de quand une modification de EditText est effectuée via un 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);
}