¿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.

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


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

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


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

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

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
}

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

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

3

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

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

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

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

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)


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

0

Muy simple, configure texto con este método

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

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


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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.