Làm cách nào để thay đổi văn bản EditText mà không cần kích hoạt Trình xem văn bản?


104

Tôi có một EditTexttrường với Trình theo dõi văn bản của khách hàng trên đó. Trong một đoạn mã, tôi cần thay đổi giá trị trong EditText mà tôi đang sử dụng .setText("whatever").

Vấn đề là ngay sau khi tôi thực hiện thay đổi đó, afterTextChangedphương thức được gọi, tạo ra một vòng lặp vô hạn. Làm cách nào để thay đổi văn bản mà không cần kích hoạt afterTextChanged?

Tôi cần văn bản trong phương thức afterTextChanged, vì vậy tôi không đề xuất xóa TextWatcher.

Câu trả lời:


70

Bạn có thể hủy đăng ký người theo dõi, sau đó đăng ký lại.

Ngoài ra, bạn có thể đặt cờ để người theo dõi của bạn biết khi nào bạn vừa tự thay đổi văn bản (và do đó nên bỏ qua nó).


Gợi ý của bạn là đủ cho tôi. Trên thực tế, tôi đang đăng ký trình nghe trong onResume nhưng không hủy đăng ký trong onPause (), vì vậy nó đang gọi nhiều lần.
Smeet

ai đó có thể giải thích nó không? về mặt kỹ thuật, tôi là người mới trong Android, xin vui lòng chi tiết hơn, cảm ơn.
Budi Mulyo

116

Câu trả lời ngắn

Bạn có thể kiểm tra Chế độ xem nào hiện có tiêu điểm để phân biệt giữa sự kiện do người dùng và chương trình kích hoạt.

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

    //...
});

Câu trả lời dài

Như một phần bổ sung cho câu trả lời ngắn gọn: Trong trường hợp myEditTextđã có tiêu điểm khi bạn thay đổi theo chương trình văn bản bạn nên gọi clearFocus(), thì bạn gọi setText(...)và sau khi bạn yêu cầu lại tiêu điểm. Sẽ là một ý kiến ​​hay nếu đặt nó vào một chức năng tiện ích:

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

Đối với Kotlin:

Vì Kotlin hỗ trợ các chức năng mở rộng, chức năng tiện ích của bạn có thể trông giống như sau:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}

trong mảnh vỡ getActivity().getCurrentFocus()và kotlinactivity?.currentFocus
Mohammad Reza Khahani

@MohammadRezaKhahani Này! bạn không cần điều đó. Bạn có thể gọi hasFocus()trực tiếp trên EditText.
Willi Mentzel

Không lý tưởng. Điều gì sẽ xảy ra nếu tôi muốn setText () mà không có trình nghe kích hoạt ngay cả khi edittext có tiêu điểm?
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);
    }
}

Sử dụng:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Bạn có thể cảm thấy hơi trễ khi nhập nhanh văn bản nếu bạn đang sử dụng editText.setText () thay vì editable.replace () .


Tại sao điều này bị phản đối? Đây là giải pháp hoàn hảo cho vấn đề của tôi trong khi những giải pháp khác không hoạt động. Tuy nhiên, tôi sẽ thêm rằng không cần thiết phải phân lớp TextWatcher để giải pháp này hoạt động. Cảm ơn Chan Chun Him.
Michael Fulton,

Ý tưởng của bạn để hủy đăng ký và đăng ký lại TextWatcher hoạt động rất tốt. Tuy nhiên, trong khi et.setText ("") hoạt động, s.replace (0, s.length (), "") thì không.
stevehs 17

@ stevehs17, tôi thực sự không biết mã của bạn thế nào nhưng cách sử dụng đã được cập nhật rất chi tiết, hãy thử.
Chan Chun Him

15

Mẹo dễ sửa ... miễn là logic của bạn để lấy ra giá trị văn bản chỉnh sửa mới là không cần thiết (có thể đúng như vậy, nhưng chỉ cần nói). Trong phương pháp trình nghe của bạn, chỉ sửa đổi văn bản chỉnh sửa nếu giá trị hiện tại khác với lần cuối cùng bạn sửa đổi giá trị.

ví dụ,

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
Điều này vẫn sẽ dẫn đến phương thức được gọi hai lần, tuy nhiên, không?
Trevor Hart

Vâng, đó là lý do tại sao tôi đã nói rằng nó phải là Idempotent (lẽ ra phải nói rõ hơn) ... không lý tưởng nhưng bạn phải thực sự tối ưu hóa mạnh mẽ nếu bạn lo lắng về chi phí của một lệnh gọi phương thức duy nhất không hoạt động. Nếu vậy, bạn có thể sắp xếp lại mã để xóa trình nghe trước khi nó gọi setText()và thêm lại sau đó.
Jeffrey Blattman,

6

Tôi sử dụng cách đó:

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

Và mỗi khi bạn cần thay đổi văn bản theo chương trình, trước tiên hãy xóa tiêu điểm

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);

5

Bạn có thể sử dụng cú pháp Kotlin DSL để có giải pháp chung cho việc này:

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

Và bên trong TextWatcher của bạn, bạn có thể sử dụng nó như:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}

Sạch hơn nhiều. Kotlin DSL thật tuyệt vời
Anjal Saneen

4

Điều này làm việc tốt cho tôi

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

Vấn đề có thể được giải quyết dễ dàng bằng cách sử dụng tag tệp tin và bạn thậm chí không phải đối phó với trọng tâm của editText.

Đặt văn bản và thẻ theo chương trình

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

Kiểm tra nội tagdung onTextChanged

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

3

Nếu bạn cần tập trung vào EditTextvăn bản thay đổi, bạn có thể yêu cầu tiêu điểm:

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

1

hãy thử logic này: Tôi muốn setText ("") mà không cần đi đến vòng lặp vô hạn và mã này phù hợp với tôi. Tôi hy vọng bạn có thể sửa đổi điều này để phù hợp với yêu cầu của bạn

        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

Đây là một lớp tiện dụng cung cấp giao diện đơn giản hơn TextWatcher cho trường hợp thông thường là muốn xem các thay đổi khi chúng xảy ra. Nó cũng cho phép bỏ qua thay đổi tiếp theo như OP yêu cầu.

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

Sử dụng nó như thế này:

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

Bất cứ khi nào bạn muốn sửa đổi nội dung của editTextmà không gây ra một loạt các chỉnh sửa đệ quy, hãy làm như sau:

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

0

Biến thể của tôi:

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


}

Chỉ đặt trình nghe bằng setOnTextChangeListener () và chỉ đặt văn bản bằng setNewText (Tôi muốn ghi đè setText (), nhưng nó là cuối cùng)


0

Tôi đã tạo một lớp trừu tượng giúp giảm thiểu vấn đề chu kỳ khi sửa đổi EditText được thực hiện thông qua 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

Rất đơn giản, đặt văn bản bằng phương pháp này

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

-2

Bạn nên đảm bảo việc triển khai các thay đổi văn bản của mình ổn định và không thay đổi văn bản nếu không cần thay đổi. Thông thường, đó sẽ là bất kỳ nội dung nào đã qua người xem một lần.

Sai lầm phổ biến nhất là đặt văn bản mới trong EditText được liên kết hoặc Editable mặc dù văn bản không thực sự thay đổi.

Trên hết, nếu bạn thực hiện các thay đổi đối với Chế độ xem có thể chỉnh sửa thay vì một số Chế độ xem cụ thể, bạn có thể dễ dàng sử dụng lại trình xem của mình và bạn cũng có thể kiểm tra nó một cách riêng biệt với một số bài kiểm tra đơn vị để đảm bảo nó có kết quả như bạn muốn.

Vì Editable là một giao diện, bạn thậm chí có thể sử dụng một triển khai giả của nó để ném RuntimeException nếu bất kỳ phương thức nào của nó được gọi là cố gắng thay đổi nội dung của nó khi kiểm tra nội dung phải ổn định.


-2

Cách của tôi để làm điều này:

Trong đoạn viết

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

Người nghe

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

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

        ....

Vẫn hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.