Wie kann ich den EditText-Text ändern, ohne den Text Watcher auszulösen?

104

ich habe ein EditText Feld mit einem Customer Text Watcher. In einem Code muss ich den Wert im EditText ändern, den ich verwende .setText("whatever").

Das Problem ist, sobald ich das ändere afterTextChanged Methode aufgerufen, die eine Endlosschleife erzeugt. Wie kann ich den Text ändern, ohne dass afterTextChanged ausgelöst wird?

Ich brauche den Text in der afterTextChanged-Methode, also schlage nicht vor, den zu entfernen TextWatcher.

user1143767
quelle

Antworten:

70

Sie können die Registrierung des Beobachters aufheben und ihn dann erneut registrieren.

Alternativ können Sie ein Flag setzen, damit Ihr Beobachter weiß, wann Sie den Text gerade selbst geändert haben (und ihn daher ignorieren sollten).

CasualT
quelle
Dein Hinweis reicht mir. Eigentlich habe ich den Listener in onResume registriert, aber nicht in onPause () abgemeldet, sodass er mehrmals aufgerufen hat.
Smeet
Kann jemand es erklären? Technisch gesehen bin ich neu in Android, brauche mehr Details. Bitte, danke.
Budi Mulyo
116

Kurze Antwort

Sie können überprüfen, welche Ansicht derzeit den Fokus hat, um zwischen benutzer- und programmgesteuerten Ereignissen zu unterscheiden.

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

    //...
});

Lange Antwort

Als Ergänzung zur kurzen Antwort: Falls myEditTextder Fokus bereits vorhanden ist, wenn Sie den aufzurufenden Text programmgesteuert ändern, rufen clearFocus()Sie an setText(...)und nachdem Sie den Fokus erneut angefordert haben. Es wäre eine gute Idee, dies in eine Utility-Funktion zu integrieren:

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

Für Kotlin:

Da Kotlin Erweiterungsfunktionen unterstützt, könnte Ihre Utility-Funktion folgendermaßen aussehen:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
Willi Mentzel
quelle
in Fragment getActivity().getCurrentFocus()und Kotlinactivity?.currentFocus
Mohammad Reza Khahani
@MohammadRezaKhahani Hey! das brauchst du nicht Sie können hasFocus()direkt auf der anrufen EditText.
Willi Mentzel
Nicht ideal. Was ist, wenn ich Text () ohne Trigger-Listener setzen möchte, auch wenn edittext den Fokus hat?
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);
    }
}

Verwendung:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Sie können ein wenig Verzögerung fühlen , wenn die Eingabe von Text schnell , wenn Sie mit editText.setText () statt editable.replace () .

Chan Chun ihn
quelle
Warum wurde das abgelehnt? Dies ist die perfekte Lösung für mein Problem, während die anderen nicht funktionierten. Ich werde hinzufügen, dass es nicht erforderlich ist, TextWatcher zu unterordnen, damit diese Lösung funktioniert. Danke Chan Chun Ihm.
Michael Fulton
Ihre Idee, die Registrierung des TextWatchers aufzuheben und erneut zu registrieren, funktioniert hervorragend. Während et.setText ("") funktioniert, funktioniert s.replace (0, s.length (), "") nicht.
stevehs17
@ stevehs17, ich weiß nicht wirklich, wie dein Code ist, aber die Verwendung wurde sehr detailliert aktualisiert, probieren Sie es aus.
Chan Chun ihn
15

Einfacher Trick zu beheben ... solange Ihre Logik zum Ableiten des neuen Bearbeitungs-Textwerts idempotent ist (was es wahrscheinlich wäre, aber nur zu sagen). Ändern Sie in Ihrer Listener-Methode den Bearbeitungstext nur, wenn sich der aktuelle Wert von dem Wert unterscheidet, den Sie zuletzt geändert haben.

z.B,

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
quelle
1
Dies führt immer noch dazu, dass die Methode zweimal aufgerufen wird.
Trevor Hart
Ja, deshalb habe ich gesagt, es sollte idempotent sein (hätte das klarer machen sollen) ... nicht ideal, aber Sie müssen wirklich hardcore optimieren, wenn Sie sich Sorgen über den Overhead eines einzelnen Methodenaufrufs machen, der nicht funktioniert. In diesem Fall können Sie den Code neu organisieren, um den Listener vor dem Aufruf zu entfernen setText()und ihn anschließend erneut hinzuzufügen.
Jeffrey Blattman
6

Ich benutze diesen Weg:

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

Und jedes Mal, wenn Sie Text programmgesteuert ändern müssen, löschen Sie zuerst den Fokus

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

Sie können die Kotlin DSL-Syntax verwenden, um die generische Lösung dafür zu erhalten:

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

In Ihrem TextWatcher können Sie Folgendes verwenden:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
Alex Misiulia
quelle
Viel sauberer. Kotlin DSL ist fantastisch
Anjal Saneen
4

Das funktioniert gut für mich

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

Das Problem kann einfach mit " tagFiled" gelöst werden , und Sie müssen sich nicht einmal mit dem Fokus von editText befassen.

Programmgesteuertes Einstellen von Text und Tag

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

Überprüfung auf die tagin OnTextChanged

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

Wenn Sie sich auf das EditTextÄndern von Text konzentrieren müssen, können Sie den Fokus anfordern:

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

Versuchen Sie diese Logik: Ich wollteText ("") setzen, ohne in die Endlosschleife zu gehen, und dieser Code funktioniert für mich. Ich hoffe, Sie können dies an Ihre Anforderungen anpassen

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

Hier ist eine praktische Klasse, die eine einfachere Oberfläche als TextWatcher bietet, wenn Sie normalerweise Änderungen sehen möchten, sobald sie auftreten. Es ermöglicht auch das Ignorieren der nächsten Änderung, wenn das OP angefordert wird.

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

Verwenden Sie es so:

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

Wenn Sie den Inhalt von ändern möchten, editTextohne eine Kaskade rekursiver Änderungen zu verursachen, gehen Sie folgendermaßen vor:

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

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


}

Stellen Sie Listener nur mit setOnTextChangeListener () und Text nur mit setNewText ein (ich wollte setText () überschreiben, aber es ist endgültig)

Kandi
quelle
0

Ich habe eine abstrakte Klasse erstellt, die das zyklische Problem verringert, wenn eine Änderung am EditText über einen TextWatcher vorgenommen wird.

/**
 * 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
quelle
0

Sehr einfach, setzen Sie Text mit dieser Methode

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

Sie sollten sicherstellen, dass Ihre Implementierung von Textänderungen stabil ist und den Text nicht ändert, wenn keine Änderung erforderlich ist. Normalerweise ist das jeder Inhalt, der den Beobachter bereits einmal durchlaufen hat.

Der häufigste Fehler besteht darin, einen neuen Text im zugehörigen EditText oder im Editable festzulegen, obwohl der Text nicht tatsächlich geändert wurde.

Wenn Sie Ihre Änderungen an der bearbeitbaren Ansicht anstelle einer bestimmten Ansicht vornehmen, können Sie Ihren Watcher außerdem problemlos wiederverwenden und ihn mit einigen Komponententests isoliert testen, um sicherzustellen, dass das gewünschte Ergebnis erzielt wird.

Da Editable eine Schnittstelle ist, können Sie sogar eine Dummy-Implementierung verwenden, die eine RuntimeException auslöst, wenn eine ihrer Methoden aufgerufen wird, die versuchen, ihren Inhalt zu ändern, wenn Sie Inhalte testen, die stabil sein sollen.

thoutbeckers
quelle
-2

Mein Weg, das Ding zu machen:

Im Schreibsegment

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

Der Zuhörer

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

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

        ....

Funktioniert trotzdem.

Paolo
quelle