¿Cómo puedo cambiar el texto EditText sin activar Text Watcher?

104

Tengo un EditTextcampo con un Customer Text Watcher. En un fragmento de código, necesito cambiar el valor en el EditText que uso .setText("whatever").

El problema es que tan pronto como hago ese cambio afterTextChanged, se llama al método que crea un bucle infinito. ¿Cómo puedo cambiar el texto sin que se active afterTextChanged?

Necesito el texto en el método afterTextChanged, así que no sugiera eliminar el TextWatcher.

usuario1143767
fuente

Respuestas:

70

Puede cancelar el registro del observador y luego volver a registrarlo.

Alternativamente, puede establecer una bandera para que su observador sepa cuándo acaba de cambiar el texto usted mismo (y, por lo tanto, debe ignorarlo).

CasualT
fuente
Tu sugerencia es suficiente para mí. En realidad, estaba registrando al oyente en onResume pero no dando de baja en onPause (), por lo que estaba llamando varias veces.
Smeet
¿Alguien puede explicarlo? Técnicamente, soy nuevo en Android, necesito más detalles, gracias.
Budi Mulyo
116

Respuesta corta

Puede comprobar qué Vista tiene actualmente el foco para distinguir entre los eventos activados por el usuario y el 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
        }
    }

    //...
});

Respuesta larga

Como una adición a la respuesta corta: en caso de que myEditTextya tenga el enfoque cuando cambia programáticamente el texto al que debe llamar clearFocus(), luego llama setText(...)y luego vuelve a solicitar el enfoque. Sería una buena idea poner eso en una función de utilidad:

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

Para Kotlin:

Dado que Kotlin admite funciones de extensión, su función de utilidad podría verse así:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
Willi Mentzel
fuente
en fragmento getActivity().getCurrentFocus()y kotlinactivity?.currentFocus
Mohammad Reza Khahani
@MohammadRezaKhahani ¡Oye! no necesitas eso. Puede llamar hasFocus()directamente al EditText.
Willi Mentzel
No es ideal. ¿Qué pasa si quiero setText () sin un oyente de activación incluso si edittext tiene el 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));

Puede sentir un poco de retraso al ingresar texto rápidamente si está utilizando editText.setText () en lugar de editable.replace () .

Chan Chun Him
fuente
¿Por qué se votó en contra? Esta es la solución perfecta a mi problema, mientras que los demás no funcionaron. Sin embargo, agregaré que no es necesario subclasificar TextWatcher para que esta solución funcione. Gracias Chan Chun él.
Michael Fulton
Su idea para anular el registro y volver a registrar el TextWatcher funciona muy bien. Sin embargo, mientras et.setText ("") funciona, s.replace (0, s.length (), "") no.
stevehs17
@ stevehs17, realmente no sé cómo está tu código, pero el uso se actualizó con mucho detalle, inténtalo.
Chan Chun Him
15

Truco fácil de arreglar ... siempre que su lógica para derivar el nuevo valor de texto de edición sea idempotente (lo que probablemente sería, pero solo digo). En su método de escucha, solo modifique el texto de edición si el valor actual es diferente al de la última vez que modificó el valor.

p.ej,

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
fuente
1
Sin embargo, esto dará como resultado que el método se llame dos veces, ¿no?
Trevor Hart
Sí, por eso dije que debería ser idempotente (debería haberlo dejado más claro) ... no es ideal, pero tienes que ser realmente una optimización extrema si te preocupa la sobrecarga de una sola llamada a un método que no funciona. Si es así, puede reorganizar el código para eliminar el oyente antes de que llame setText()y volver a agregarlo después.
Jeffrey Blattman
6

Yo uso de esa manera:

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

Y cada vez que necesite cambiar el texto mediante programación, primero borre el enfoque

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

Puede usar la sintaxis DSL de Kotlin para tener la solución genérica para esto:

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

Y dentro de su TextWatcher, puede usarlo como:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
Alex Misiulia
fuente
Mucho más limpio. Kotlin DSL es increíble
Anjal Saneen
4

Esto funciona bien para mi

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

El problema se puede resolver fácilmente usando tagarchivado y ni siquiera tiene que lidiar con el enfoque de editText.

Establecer el texto y la etiqueta mediante programación

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

Comprobando el tagonTextChanged

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

Si necesita concentrarse en EditTextcambiar el texto, puede solicitar el enfoque:

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

prueba esta lógica: quería establecer setText ("") sin ir al bucle infinito y este código funciona para mí. Espero que pueda modificar esto para que se ajuste a sus necesidades.

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

Aquí hay una clase útil que proporciona una interfaz más simple que TextWatcher para el caso normal de querer ver los cambios a medida que ocurren. También permite ignorar el siguiente cambio solicitado por el 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());
            }
        }
    }
}

Úselo así:

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

Siempre que desee modificar el contenido de editTextsin provocar una cascada de ediciones recursivas, haga lo siguiente:

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

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


}

Configure oyentes solo usando setOnTextChangeListener () y configure texto solo usando setNewText (quería anular setText (), pero es final)

kandi
fuente
0

He creado una clase abstracta que mitiga el problema cíclico de cuándo se realiza una modificación al EditText a través de 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
fuente
0

Muy simple, configure texto con este método

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

Debe asegurarse de que la implementación de los cambios de texto sea estable y no cambie el texto si no se necesita ningún cambio. Normalmente, ese sería cualquier contenido que ya haya pasado por el observador una vez.

El error más común es establecer un nuevo texto en el EditText asociado o en el Editable aunque el texto no haya sido modificado.

Además de eso, si realiza sus cambios en Editable en lugar de en una Vista específica, puede volver a utilizar fácilmente su observador, y también puede probarlo de forma aislada con algunas pruebas unitarias para asegurarse de que tenga el resultado que desea.

Dado que Editable es una interfaz, incluso podría usar una implementación ficticia de la misma que arroje una RuntimeException si se llama a alguno de sus métodos para intentar cambiar su contenido, al probar contenido que debería ser estable.

thoutbeckers
fuente
-2

Mi forma de hacer las cosas:

En el segmento de escritura

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

El oyente

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

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

        ....

Funciona de todos modos.

Paolo
fuente