Como posso alterar o texto EditText sem acionar o Text Watcher?

104

Eu tenho um EditTextcampo com um Observador de Texto do Cliente. Em um trecho de código, preciso alterar o valor no EditText que utilizo .setText("whatever").

O problema é que, assim que faço essa alteração, o afterTextChangedmétodo é chamado, o que cria um loop infinito. Como posso alterar o texto sem acionar afterTextChanged?

Preciso do texto no método afterTextChanged, portanto, não sugiro remover o TextWatcher.

user1143767
fonte

Respostas:

70

Você pode cancelar o registro do observador e, em seguida, registrá-lo novamente.

Alternativamente, você pode definir um sinalizador para que seu observador saiba quando você acabou de alterar o texto (e, portanto, deve ignorá-lo).

CasualT
fonte
Sua dica é o suficiente para mim. Na verdade, eu estava registrando o ouvinte em onResume, mas não cancelando o registro em onPause (), então ele estava chamando várias vezes.
Smeet de
Alguém pode explicar isso? Tecnicamente, eu sou novo no Android, preciso de mais detalhes, por favor, obrigado.
Budi Mulyo de
116

Resposta curta

Você pode verificar qual Visualização tem o foco atualmente para distinguir entre os eventos disparados pelo usuário e pelo programa.

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

    //...
});

Resposta longa

Como complemento à resposta curta: Caso myEditTextjá tenha o foco quando você alterar programaticamente o texto que deve chamar clearFocus(), então você liga setText(...)e depois pede novamente o foco. Seria uma boa ideia colocar isso em uma função de utilidade:

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

Para Kotlin:

Como o Kotlin oferece suporte a funções de extensão, sua função de utilitário pode ser parecida com esta:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
Willi Mentzel
fonte
no fragmento getActivity().getCurrentFocus()e kotlinactivity?.currentFocus
Mohammad Reza Khahani
@MohammadRezaKhahani Ei! você não precisa disso. Você pode ligar hasFocus()diretamente no EditText.
Willi Mentzel
Não é ideal. E se eu quiser setText () sem o listener de gatilho, mesmo se o edittext estiver em foco?
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));

Você pode sentir um pequeno atraso ao inserir texto rapidamente se estiver usando editText.setText () em vez de editable.replace () .

Chan Chun Him
fonte
Por que isso foi rejeitado? Esta é a solução perfeita para o meu problema, enquanto as outras não funcionaram. Acrescentarei que não é necessário criar uma subclasse de TextWatcher para que esta solução funcione. Obrigado Chan Chun Him.
Michael Fulton
Sua ideia de cancelar e registrar novamente o TextWatcher funciona muito bem. No entanto, embora et.setText ("") funcione, s.replace (0, s.length (), "") não.
stevehs17
@ stevehs17, eu realmente não sei como está o seu código, mas o uso foi atualizado muito detalhadamente, tente.
Chan Chun Him
15

Truque fácil de corrigir ... desde que sua lógica para derivar o novo valor do texto de edição seja idempotente (o que provavelmente seria, mas apenas dizendo). Em seu método de ouvinte, modifique o texto de edição apenas se o valor atual for diferente da última vez que você modificou o valor.

por exemplo,

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
fonte
1
Isso ainda resultará no método sendo chamado duas vezes, não?
Trevor Hart
Sim, é por isso que afirmei que deveria ser idempotente (deveria ter deixado isso mais claro) ... não é o ideal, mas você realmente tem que ser hardcore otimizando se estiver preocupado com a sobrecarga de uma única chamada de método que não funciona. Nesse caso, você pode reorganizar o código para remover o ouvinte antes de chamar setText()e adicioná-lo novamente depois.
Jeffrey Blattman
6

Eu uso assim:

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 toda vez que você precisar alterar o texto programaticamente, primeiro limpe o foco

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);
Arthur melo
fonte
5

Você pode usar a sintaxe DSL do Kotlin para ter a solução genérica para isso:

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

E dentro de seu TextWatcher, você pode usá-lo como:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
Alex Misiulia
fonte
Muito mais limpo. Kotlin DSL é incrível
Anjal Saneen
4

Isso funciona bem para mim

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
fonte
3

O problema pode ser facilmente resolvido usando tagarquivado e você nem precisa lidar com o foco do editText.

Definir o texto e a tag de forma programática

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

Verificando tagonTextChanged

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

Se você precisa se concentrar na EditTextalteração do texto, pode solicitar o foco:

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

tente esta lógica: eu queria setText ("") sem ir para o loop infinito e este código funciona para mim. Espero que você possa modificar isso para atender às suas necessidades

        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
fonte
1

Aqui está uma classe útil que fornece uma interface mais simples do que TextWatcher para o caso normal de querer ver as alterações conforme elas ocorrem. Também permite ignorar a próxima alteração conforme solicitado pelo 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());
            }
        }
    }
}

Use-o assim:

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

Sempre que quiser modificar o conteúdo editTextsem causar uma cascata de edições recursivas, faça o seguinte:

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

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


}

Defina ouvintes apenas usando setOnTextChangeListener () e defina texto apenas usando setNewText (eu queria substituir setText (), mas é o final)

Kandi
fonte
0

Eu criei uma classe abstrata que atenua o problema cíclico de quando uma modificação no EditText é feita por meio de um 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
fonte
0

Muito simples, defina o texto com este método

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

Você deve garantir que sua implementação de alterações de texto seja estável e não altere o texto se nenhuma alteração for necessária. Normalmente, seria qualquer conteúdo que já tenha passado pelo observador uma vez.

O erro mais comum é definir um novo texto no EditText associado ou no Editable, mesmo que o texto não tenha sido realmente alterado.

Além disso, se você fizer alterações no Editável em vez de em alguma Visualização específica, pode facilmente reutilizar seu observador e também pode testá-lo isoladamente com alguns testes de unidade para garantir que tenha o resultado desejado.

Como Editable é uma interface, você pode até usar uma implementação fictícia dela que lança uma RuntimeException se algum de seus métodos for chamado para tentar alterar seu conteúdo, ao testar um conteúdo que deve ser estável.

thoutbeckers
fonte
-2

Minha maneira de fazer as coisas:

No segmento de gravação

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

O ouvinte

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

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

        ....

Funciona mesmo assim.

Paolo
fonte