Jak mogę zmienić tekst EditText bez wyzwalania Text Watchera?

104

Mam EditTextpole z obserwatorem tekstu klienta. W fragmencie kodu muszę zmienić wartość w EditText, którego używam .setText("whatever").

Problem polega na tym, że gdy tylko dokonam tej zmiany afterTextChanged, zostanie wywołana metoda, która utworzyła nieskończoną pętlę. Jak mogę zmienić tekst bez uruchamiania go po zmianie tekstu?

Potrzebuję tekstu w metodzie afterTextChanged, więc nie sugeruj usuwania TextWatcher.

user1143767
źródło

Odpowiedzi:

70

Możesz wyrejestrować obserwatora, a następnie zarejestrować go ponownie.

Alternatywnie, możesz ustawić flagę, aby Twój obserwator wiedział, kiedy sam zmieniłeś tekst (i dlatego powinien go zignorować).

CasualT
źródło
Twoja wskazówka mi wystarczy. Właściwie rejestrowałem słuchacza w onResume, ale nie wyrejestrowywałem się w onPause (), więc dzwonił wiele razy.
Smeet
Czy ktoś może to wyjaśnić? technicznie, jestem nowy w Androidzie, potrzebuję więcej szczegółów, dzięki.
Budi Mulyo
116

Krótka odpowiedź

Możesz sprawdzić, który widok ma aktualnie fokus, aby rozróżniać zdarzenia wyzwalane przez użytkownika i program.

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

    //...
});

Długa odpowiedź

Jako dodatek do krótkiej odpowiedzi: w przypadku, gdy myEditTextjuż ma fokus, gdy programistycznie zmieniasz tekst, który powinieneś wywołać clearFocus(), dzwonisz setText(...)i po ponownym zażądaniu fokusu. Dobrym pomysłem byłoby umieszczenie tego w funkcji narzędzia:

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

Kotlin:

Ponieważ Kotlin obsługuje funkcje rozszerzeń, twoja funkcja narzędziowa mogłaby wyglądać następująco:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
Willi Mentzel
źródło
we fragmencie getActivity().getCurrentFocus()i kotlinactivity?.currentFocus
Mohammad Reza Khahani
@MohammadRezaKhahani Hey! nie potrzebujesz tego. Możesz zadzwonić hasFocus()bezpośrednio na EditText.
Willi Mentzel
Nieidealny. Co jeśli chcę ustawićText () bez detektora wyzwalacza, nawet jeśli fokus jest ustawiony na edittext?
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);
    }
}

Stosowanie:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Możesz odczuwać niewielkie opóźnienie podczas szybkiego wprowadzania tekstu, jeśli używasz editText.setText () zamiast editable.replace () .

Chan Chun Go
źródło
Dlaczego ten głos został odrzucony? To idealne rozwiązanie mojego problemu, podczas gdy inne nie działały. Dodam, że nie jest konieczne tworzenie podklasy TextWatchera, aby to rozwiązanie działało. Dzięki Chan Chun Go.
Michael Fulton
Twój pomysł na wyrejestrowanie i ponowną rejestrację TextWatchera działa świetnie. Jednakże, chociaż et.setText ("") działa, s.replace (0, s.length (), "") nie.
stevehs17
@ stevehs17, naprawdę nie wiem, jak wygląda twój kod, ale sposób użycia został bardzo szczegółowo zaktualizowany, spróbuj.
Chan Chun Him
15

Łatwa sztuczka do naprawienia ... o ile logika wyprowadzenia nowej wartości tekstu edycji jest idempotentna (co prawdopodobnie byłoby, ale tylko mówienie). W metodzie detektora modyfikuj tekst edycji tylko wtedy, gdy bieżąca wartość jest inna niż ostatnia modyfikacja wartości.

na przykład,

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
źródło
1
Jednak nadal spowoduje to dwukrotne wywołanie metody, prawda?
Trevor Hart
Tak, dlatego stwierdziłem, że powinno to być idempotentne (powinno było to wyjaśnić) ... nie idealne, ale musisz być naprawdę hardkorowo optymalizujący, jeśli martwisz się o narzut pojedynczego wywołania metody, który nie działa. Jeśli tak, możesz ponownie zorganizować kod, aby usunąć detektor przed wywołaniem setText()i dodać go ponownie po.
Jeffrey Blattman
6

Używam w ten sposób:

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

Za każdym razem, gdy musisz programowo zmienić tekst, najpierw wyczyść fokus

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);
Arthur Melo
źródło
5

Możesz użyć składni Kotlin DSL, aby uzyskać ogólne rozwiązanie:

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

Wewnątrz swojego TextWatchera możesz go używać jako:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
Alex Misiulia
źródło
Dużo czystsze. Kotlin DSL jest niesamowity
Anjal Saneen
4

To działa dobrze dla mnie

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
źródło
3

Problem można łatwo rozwiązać za pomocą tagpola filed i nie musisz nawet zajmować się aktywnością editText.

Programowe ustawianie tekstu i tagu

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

Sprawdzanie tagw onTextChanged

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

Jeśli chcesz skupić się na EditTexttekście zmiany, możesz poprosić o fokus:

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}
Milos Simic Simo
źródło
1

wypróbuj tę logikę: chciałem ustawić tekst ("") bez przechodzenia do nieskończonej pętli, a ten kod działa dla mnie. Mam nadzieję, że możesz to zmodyfikować, aby pasowało do twoich wymagań

        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
źródło
1

Oto przydatna klasa, która zapewnia prostszy interfejs niż TextWatcher w normalnym przypadku, gdy chcesz zobaczyć zmiany w momencie ich wystąpienia. Pozwala również na ignorowanie następnej zmiany zgodnie z żądaniem PO.

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

Użyj tego w ten sposób:

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

Jeśli chcesz zmodyfikować zawartość editTextbez powodowania kaskady rekurencyjnych edycji, zrób to:

listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener
JohnnyLambada
źródło
0

Mój wariant:

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


}

Ustaw detektory tylko za pomocą setOnTextChangeListener () i ustaw tekst tylko za pomocą setNewText (chciałem przesłonić setText (), ale jest to ostateczne)

kandi
źródło
0

Stworzyłem abstrakcyjną klasę, która łagodzi cykliczny problem, gdy modyfikacja EditText jest dokonywana za pośrednictwem TextWatchera.

/**
 * 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
źródło
0

Bardzo prosty, ustaw tekst za pomocą tej metody

void updateText(EditText et, String text) {
   if (!et.getText().toString().equals(text))
       et.setText(text);
}
utrucceh
źródło
-2

Powinieneś upewnić się, że wprowadzanie zmian w tekście jest stabilne i nie zmienia tekstu, jeśli żadna zmiana nie jest potrzebna. Zwykle byłaby to każda treść, która już raz przeszła przez obserwatora.

Najczęstszym błędem jest ustawienie nowego tekstu w powiązanym EditText lub Editable, mimo że tekst w rzeczywistości nie został zmieniony.

Ponadto, jeśli wprowadzisz zmiany w Edytowalnym zamiast w jakimś konkretnym widoku, możesz łatwo ponownie użyć swojego obserwatora, a także możesz przetestować go w izolacji za pomocą niektórych testów jednostkowych, aby upewnić się, że ma oczekiwany wynik.

Ponieważ Editable jest interfejsem, możesz nawet użyć jego fałszywej implementacji, która generuje wyjątek RuntimeException, jeśli zostanie wywołana którakolwiek z jego metod próbujących zmienić jego zawartość, podczas testowania zawartości, która powinna być stabilna.

thoutbeckers
źródło
-2

Mój sposób na zrobienie tego:

W segmencie zapisu

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

Słuchacz

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

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

        ....

Działa i tak.

Paolo
źródło