Comment puis-je modifier le texte EditText sans déclencher le Text Watcher?

104

J'ai un EditTextchamp avec un observateur de texte client dessus. Dans un morceau de code, j'ai besoin de changer la valeur du EditText que j'utilise .setText("whatever").

Le problème est que dès que je fais ce changement, la afterTextChangedméthode est appelée, ce qui crée une boucle infinie. Comment puis-je changer le texte sans qu'il ne déclenche afterTextChanged?

J'ai besoin du texte dans la méthode afterTextChanged, alors ne suggérez pas de supprimer le fichier TextWatcher.

user1143767
la source

Réponses:

70

Vous pouvez désinscrire 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 devrait donc l'ignorer).

CasualT
la source
Votre indice me suffit. En fait, j'enregistrais l'auditeur dans onResume mais je ne me désinscrivais pas dans onPause (), donc il appelait plusieurs fois.
Smeet
quelqu'un peut-il l'expliquer? Techniquement, je suis nouveau dans Android, j'ai besoin de plus de détails, merci.
Budi Mulyo
116

Réponse courte

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 (myEditText.hasFocus()) {
            // is only executed if the EditText was directly changed by the user
        }
    }

    //...
});

Longue réponse

En complément de la réponse courte: dans le cas où a myEditTextdéjà le focus lorsque vous modifiez par programme le texte que vous devez appeler clearFocus(), alors vous appelez setText(...)et après vous re-demandez le focus. Ce serait une bonne idée de mettre cela dans une fonction d'utilité:

void updateText(EditText editText, String text) {
    boolean focussed = editText.hasFocus();
    if (focussed) {
        editText.clearFocus();
    }
    editText.setText(text);
    if (focussed) {
        editText.requestFocus();
    }
}

Pour Kotlin:

Puisque Kotlin prend en charge les fonctions d'extension, votre fonction utilitaire pourrait ressembler à ceci:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
Willi Mentzel
la source
in fragment getActivity().getCurrentFocus()and kotlinactivity?.currentFocus
Mohammad Reza Khahani
@MohammadRezaKhahani Hé! vous n'en avez pas besoin. Vous pouvez appeler hasFocus()directement sur le EditText.
Willi Mentzel
Pas idéal. Que faire si je veux setText () sans déclencheur d'écoute même si edittext a le focus?
Jaya Prakash
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);
    }
}

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

Chan Chun lui
la source
Pourquoi ce vote a-t-il été rejeté? C'est la solution parfaite à mon problème alors que les autres n'ont pas fonctionné. J'ajouterai qu'il n'est pas nécessaire de sous-classer TextWatcher pour que cette solution fonctionne cependant. Merci Chan Chun Him.
Michael Fulton
Votre idée de désinscription et de réenregistrement de TextWatcher fonctionne très bien. Cependant, alors que et.setText ("") fonctionne, s.replace (0, s.length (), "") ne fonctionne pas.
stevehs17
@ stevehs17, je ne sais pas vraiment comment est votre code mais l'utilisation a été mise à jour de manière très détaillée, essayez.
Chan Chun Him
15

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 simplement en disant). Dans votre méthode d'écoute, ne modifiez le texte d'édition que 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);
    }
  }
};
Jeffrey Blattman
la source
1
Cela entraînera toujours l'appel de la méthode deux fois, non?
Trevor Hart
Oui, c'est pourquoi j'ai déclaré que cela devrait être idempotent (aurait dû le rendre plus clair) ... pas idéal, mais vous devez vraiment être optimiste si vous vous inquiétez de la surcharge d'un appel de méthode unique qui ne fonctionne pas. Si c'est le cas, vous pouvez réorganiser le code pour supprimer l'écouteur avant qu'il n'appelle setText()et le rajouter après.
Jeffrey Blattman
6

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, commencez par effacer

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);
Arthur Melo
la source
5

Vous pouvez utiliser la syntaxe DSL 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 dans votre TextWatcher, vous pouvez l'utiliser comme:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
Alex Misiulia
la source
Beaucoup plus propre. Kotlin DSL est génial
Anjal Saneen
4

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) { }
    });
user3361395
la source
3

Le problème peut être facilement résolu en utilisant tagfile et vous n'avez même pas à gérer le focus de editText.

Définition du texte et de la balise par programmation

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

Vérification du tagin onTextChanged

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

Si vous avez besoin de rester concentré sur le EditTexttexte de changement, vous pouvez demander le focus:

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}
Milos Simic Simo
la source
1

essayez cette logique: je voulais setText ("") sans aller en boucle infinie et ce code fonctionne pour moi. J'espère que vous pouvez 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
        }
    });
ShAkKiR
la source
1

Voici une classe pratique qui fournit une interface plus simple que TextWatcher pour le cas normal de vouloir voir les changements au fur et à mesure qu'ils se produisent. Cela permet également d'ignorer le changement suivant comme l'OP l'a demandé.

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 editTextsans provoquer une cascade de modifications récursives, procédez comme suit:

listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener
JohnnyLambada
la source
0

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éfinir les écouteurs uniquement en utilisant setOnTextChangeListener () et définir le texte uniquement en utilisant setNewText (je voulais remplacer setText (), mais c'est définitif)

Kandi
la source
0

J'ai créé une classe abstraite qui atténue le problème cyclique du moment où une modification du 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);
}
Eurig Jones
la source
0

Très simple, définissez le texte avec cette méthode

void updateText(EditText et, String text) {
   if (!et.getText().toString().equals(text))
       et.setText(text);
}
utrucceh
la source
-2

Vous devez vous assurer que votre implémentation des modifications de texte est stable et ne change pas le texte si aucune modification n'est nécessaire. Normalement, ce serait tout contenu qui a déjà été via l'observateur une fois.

L'erreur la plus courante consiste à définir un nouveau texte dans le EditText associé ou dans Editable même si le texte n'a pas été réellement modifié.

En plus de cela, si vous apportez vos modifications à la vue modifiable au lieu d'une vue spécifique, vous pouvez facilement réutiliser votre observateur, et vous pouvez également le tester isolément avec des tests unitaires pour vous assurer qu'il a le résultat souhaité.

Comme Editable est une interface, vous pouvez même utiliser une implémentation factice qui lève une RuntimeException si l'une de ses méthodes est appelée pour essayer de changer son contenu, lors du test de contenu qui devrait être stable.

thoutbeckers
la source
-2

Ma façon de faire la chose:

Dans le segment d'écriture

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

L'auditeur

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

        int id = view.getId();
        if(id==-1)return;

        ....

Fonctionne quand même.

Paolo
la source