Come posso modificare il testo EditText senza attivare il Text Watcher?


104

Ho un EditTextcampo con un osservatore del testo del cliente. In un pezzo di codice devo cambiare il valore nell'EditText che utilizzo .setText("whatever").

Il problema è che non appena apporto la modifica afterTextChangedviene chiamato il metodo che ha creato un ciclo infinito. Come posso modificare il testo senza che si attivi afterTextChanged?

Ho bisogno del testo nel metodo afterTextChanged quindi non suggerire di rimuovere il file TextWatcher.

Risposte:


70

Potresti annullare la registrazione del watcher e quindi registrarlo nuovamente.

In alternativa, puoi impostare un flag in modo che il tuo osservatore sappia quando hai appena cambiato il testo da solo (e quindi dovrebbe ignorarlo).


Il tuo suggerimento mi basta. In realtà stavo registrando l'ascoltatore in onResume ma non stavo cancellando la registrazione in onPause (), quindi chiamava più volte.
Incontro il

qualcuno può spiegarlo? tecnicamente, sono nuovo in Android, ho bisogno di più dettagli, grazie.
Budi Mulyo

116

Risposta breve

È possibile controllare quale vista ha attualmente il focus per distinguere tra eventi attivati ​​dall'utente e dal programma.

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

    //...
});

Risposta lunga

In aggiunta alla risposta breve: nel caso in cui myEditTextabbia già il focus quando si modifica a livello di codice il testo che si dovrebbe chiamare clearFocus(), quindi si chiama setText(...)e dopo si richiede nuovamente il focus. Sarebbe una buona idea metterlo in una funzione di utilità:

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

Per Kotlin:

Poiché Kotlin supporta le funzioni di estensione, la tua funzione di utilità potrebbe essere simile a questa:

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

in frammento getActivity().getCurrentFocus()e kotlinactivity?.currentFocus
Mohammad Reza Khahani

@MohammadRezaKhani Hey! non ne hai bisogno. Puoi chiamare hasFocus()direttamente sul EditText.
Willi Mentzel,

Non è l'ideale. Cosa succede se voglio setText () senza trigger listener anche se edittext ha il 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);
    }
}

Uso:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Potresti sentire un po 'di ritardo quando inserisci il testo rapidamente se stai usando editText.setText () invece di editable.replace () .


Perché è stato downvoted? Questa è la soluzione perfetta al mio problema mentre gli altri non hanno funzionato. Aggiungerò che non è necessario creare una sottoclasse di TextWatcher affinché questa soluzione funzioni. Grazie Chan Chun Him.
Michael Fulton

La tua idea per annullare e registrare nuovamente TextWatcher funziona alla grande. Tuttavia, mentre et.setText ("") funziona, s.replace (0, s.length (), "") no.
stevehs17

@ stevehs17, non so davvero come va il tuo codice ma l'utilizzo è stato aggiornato in modo molto dettagliato, prova.
Chan Chun Him,

15

Facile trucco da risolvere ... purché la tua logica per derivare il nuovo valore di testo di modifica sia idempotente (cosa che probabilmente sarebbe, ma solo dicendo). Nel tuo metodo listener, modifica il testo di modifica solo se il valore corrente è diverso dall'ultima volta che hai modificato il valore.

per esempio,

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

1
Tuttavia, ciò comporterà comunque che il metodo venga chiamato due volte, no?
Trevor Hart

Sì, è per questo che ho affermato che dovrebbe essere idempotente (avrebbe dovuto renderlo più chiaro) ... non è l'ideale ma devi essere davvero ottimista se sei preoccupato per il sovraccarico di una singola chiamata al metodo che non funziona. In tal caso, è possibile riorganizzare il codice per rimuovere l'ascoltatore prima che chiami setText()e aggiungerlo nuovamente dopo.
Jeffrey Blattman

6

Uso in questo modo:

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

E ogni volta che è necessario modificare il testo in modo programmatico, prima cancella il focus

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

5

È possibile utilizzare la sintassi DSL di Kotlin per avere la soluzione generica per questo:

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

E all'interno del tuo TextWatcher, puoi usarlo come:

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

Molto più pulito. Kotlin DSL è fantastico
Anjal Saneen

4

Questo funziona bene per 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) { }
    });

3

Il problema può essere facilmente risolto usando tagfiled e non devi nemmeno occuparti del focus di editText.

Impostazione del testo e del tag a livello di codice

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

Controllo di tagonTextChanged

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

3

Se hai bisogno di rimanere concentrato EditText modifica del testo, puoi richiedere il focus:

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

1

prova questa logica: volevo setText ("") senza andare al ciclo infinito e questo codice funziona per me. Spero che tu possa modificarlo per soddisfare le tue esigenze

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

1

Ecco una pratica classe che fornisce un'interfaccia più semplice di TextWatcher per il caso normale di voler vedere i cambiamenti mentre si verificano. Consente inoltre di ignorare la modifica successiva come richiesto dall'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());
            }
        }
    }
}

Usalo in questo modo:

EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);

Ogni volta che vuoi modificare il contenuto di editTextsenza causare una cascata di modifiche ricorsive, fai questo:

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

0

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


}

Imposta i listener solo usando setOnTextChangeListener () e imposta solo il testo usando setNewText (volevo sovrascrivere setText (), ma è finale)


0

Ho creato una classe astratta che mitiga il problema ciclico di quando una modifica all'EditText viene effettuata tramite 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);
}

0

Molto semplice, imposta il testo con questo metodo

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

-2

È necessario assicurarsi che l'implementazione delle modifiche al testo sia stabile e non modifichi il testo se non è necessaria alcuna modifica. Normalmente si tratta di qualsiasi contenuto che è già passato attraverso l'osservatore una volta.

L'errore più comune è impostare un nuovo testo nell'EditText o nell'Modificabile associato anche se il testo non è stato effettivamente modificato.

Inoltre, se apporti le modifiche al Modificabile invece di una vista specifica, puoi facilmente riutilizzare il tuo watcher e puoi anche testarlo isolatamente con alcuni test unitari per assicurarti che abbia il risultato desiderato.

Poiché Editable è un'interfaccia, potresti persino utilizzare un'implementazione fittizia di essa che genera un'eccezione RuntimeException se viene chiamato uno qualsiasi dei suoi metodi che tentano di modificarne il contenuto, durante il test di contenuto che dovrebbe essere stabile.


-2

Il mio modo di fare la cosa:

Nel segmento di scrittura

        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'ascoltatore

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

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

        ....

Funziona comunque.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.