Bagaimana saya bisa mengubah teks EditText tanpa memicu Text Watcher?

104

Saya memiliki EditTextbidang dengan Pengamat Teks Pelanggan di atasnya. Dalam sepotong kode saya perlu mengubah nilai di EditText yang saya gunakan .setText("whatever").

Masalahnya adalah segera setelah saya melakukan perubahan itu afterTextChanged metode dipanggil yang menciptakan loop tak terbatas. Bagaimana cara mengubah teks tanpa memicu afterTextChanged?

Saya membutuhkan teks dalam metode afterTextChanged jadi jangan menyarankan menghapus file TextWatcher.

pengguna1143767
sumber

Jawaban:

70

Anda dapat membatalkan pendaftaran pengawas, lalu mendaftarkannya kembali.

Alternatifnya, Anda dapat mengatur bendera sehingga pengamat Anda tahu kapan Anda baru saja mengubah teks itu sendiri (dan karena itu harus mengabaikannya).

CasualT
sumber
Petunjukmu cukup untukku. Sebenarnya saya mendaftarkan pendengar di onResume tetapi tidak membatalkan pendaftaran di onPause (), jadi pendengar memanggil beberapa kali.
Smeet
dapatkah seseorang menjelaskannya? secara teknis, saya baru di android, perlu lebih detail, terima kasih.
Budi Mulyo
116

Jawaban singkat

Anda dapat memeriksa Tampilan mana yang saat ini memiliki fokus untuk membedakan antara peristiwa yang dipicu oleh pengguna dan 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
        }
    }

    //...
});

Jawaban panjang

Sebagai tambahan untuk jawaban singkat: Jika myEditTextsudah memiliki fokus ketika Anda secara terprogram mengubah teks yang harus Anda panggil clearFocus(), kemudian Anda memanggil setText(...)dan setelah Anda meminta ulang fokus. Ini akan menjadi ide yang bagus untuk memasukkannya ke dalam fungsi utilitas:

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

Untuk Kotlin:

Karena Kotlin mendukung fungsi ekstensi, fungsi utilitas Anda akan terlihat seperti ini:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
Willi Mentzel
sumber
dalam fragmen getActivity().getCurrentFocus()dan kotlinactivity?.currentFocus
Mohammad Reza Khahani
@MohammadRezaKhahani Hey! kamu tidak membutuhkan itu. Anda dapat menghubungi hasFocus()langsung di EditText.
Willi Mentzel
Tidak ideal. Bagaimana jika saya ingin setText () tanpa trigger listener meskipun edittext memiliki fokus?
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);
    }
}

Pemakaian:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Anda mungkin merasa sedikit lambat saat memasukkan teks dengan cepat jika Anda menggunakan editText.setText () daripada editable.replace () .

Chan Chun Him
sumber
Mengapa ini tidak disukai? Ini adalah solusi sempurna untuk masalah saya sedangkan yang lain tidak berhasil. Saya akan menambahkan bahwa tidak perlu subclass TextWatcher agar solusi ini berfungsi. Terima kasih Chan Chun Him.
Michael Fulton
Ide Anda untuk membatalkan registrasi dan registrasi ulang TextWatcher bekerja dengan baik. Namun, sementara et.setText ("") berfungsi, s.replace (0, s.length (), "") tidak.
stevehs17
@ stevehs17, saya tidak benar-benar tahu bagaimana kode Anda tetapi penggunaan diperbarui secara mendetail, cobalah.
Chan Chun Him
15

Trik mudah untuk diperbaiki ... selama logika Anda untuk mendapatkan nilai teks edit baru idempoten (yang mungkin akan terjadi, tetapi hanya mengatakan). Dalam metode listener Anda, hanya ubah teks edit jika nilai saat ini berbeda dari terakhir kali Anda mengubah nilai.

misalnya,

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
sumber
1
Ini masih akan menghasilkan metode dipanggil dua kali, bukan?
Trevor Hart
Ya, itulah mengapa saya menyatakan itu harus idempoten (seharusnya membuatnya lebih jelas) ... tidak ideal tetapi Anda harus benar-benar mengoptimalkan jika Anda khawatir tentang overhead panggilan metode tunggal yang tidak berfungsi. Jika demikian, Anda dapat mengatur ulang kode untuk menghapus pendengar sebelum memanggil setText()dan menambahkannya kembali setelahnya.
Jeffrey Blattman
6

Saya menggunakan cara itu:

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

Dan setiap kali Anda perlu mengubah teks secara programatik, pertama-tama hapus fokusnya

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

Anda dapat menggunakan sintaks Kotlin DSL untuk mendapatkan solusi umum untuk ini:

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

Dan di dalam TextWatcher Anda, Anda dapat menggunakannya sebagai:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
Alex Misiulia
sumber
Jauh lebih bersih. Kotlin DSL mengagumkan
Anjal Saneen
4

Ini bekerja dengan baik untuk saya

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

Masalahnya bisa diselesaikan dengan mudah menggunakan tag file dan Anda bahkan tidak perlu berurusan dengan fokus editText.

Menyetel teks dan tag secara terprogram

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

Memeriksa tagdi onTextChanged

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

Jika Anda perlu tetap fokus pada EditTextperubahan teks, Anda dapat meminta fokus:

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

coba logika ini: Saya ingin setText ("") tanpa pergi ke loop tak terbatas dan kode ini bekerja untuk saya. Saya harap Anda dapat memodifikasi ini agar sesuai dengan kebutuhan Anda

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

Berikut adalah kelas praktis yang menyediakan antarmuka yang lebih sederhana daripada TextWatcher untuk kasus normal yang ingin melihat perubahan saat terjadi. Ini juga memungkinkan untuk mengabaikan perubahan berikutnya seperti yang diminta 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());
            }
        }
    }
}

Gunakan seperti ini:

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

Kapan pun Anda ingin mengubah konten editTexttanpa menyebabkan rangkaian pengeditan rekursif, lakukan ini:

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

Variasi saya:

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


}

Setel pendengar hanya menggunakan setOnTextChangeListener () dan setel teks hanya menggunakan setNewText (Saya ingin mengganti setText (), tetapi ini sudah final)

kandi
sumber
0

Saya telah membuat kelas abstrak yang mengurangi masalah siklik ketika modifikasi pada EditText dibuat melalui 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
sumber
0

Sangat sederhana, atur teks dengan metode ini

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

Anda harus memastikan penerapan perubahan teks Anda stabil dan tidak mengubah teks jika tidak ada perubahan yang diperlukan. Biasanya itu akan menjadi konten apa pun yang telah melalui pengamat sekali.

Kesalahan paling umum adalah menyetel teks baru di EditText terkait atau Editable meskipun teks sebenarnya tidak berubah.

Selain itu, jika Anda membuat perubahan ke Editable daripada beberapa Tampilan tertentu, Anda dapat dengan mudah menggunakan kembali pengamat Anda, dan juga Anda dapat mengujinya secara terpisah dengan beberapa unit test untuk memastikannya memiliki hasil yang Anda inginkan.

Karena Editable adalah antarmuka, Anda bahkan dapat menggunakan implementasi tiruannya yang menampilkan RuntimeException jika salah satu metodenya dipanggil yang mencoba mengubah kontennya, saat menguji konten yang seharusnya stabil.

thoutbeckers
sumber
-2

Cara saya melakukan hal itu:

Di segmen tulis

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

Pendengar

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

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

        ....

Tetap berfungsi.

Paolo
sumber