簡體   English   中英

如何在不觸發 Text Watcher 的情況下更改 EditText 文本?

[英]How can I change the EditText text without triggering the Text Watcher?

我有一個EditText字段,上面有一個 Customer Text Watcher。 在一段代碼中,我需要更改我使用.setText("whatever")所做的 EditText 中的值。

問題是,一旦我做出更改,就會調用afterTextChanged方法,這會創建一個無限循環。 如何在不觸發 afterTextChanged 的情況下更改文本?

我需要 afterTextChanged 方法中的文本,所以不建議刪除TextWatcher

簡答

您可以檢查當前具有焦點的視圖以區分用戶和程序觸發的事件。

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

    //...
});

長答案

作為簡短回答的補充:如果myEditText在您以編程方式更改文本時已經具有焦點,則應調用clearFocus() ,然后調用setText(...)並在您重新請求焦點之后。 把它放在一個效用函數中是個好主意:

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

對於科特林:

由於 Kotlin 支持擴展函數,您的實用程序函數可能如下所示:

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

您可以取消注冊觀察者,然后重新注冊它。

或者,您可以設置一個標志,以便您的觀察者知道您何時自己更改了文本(因此應該忽略它)。

爪哇:

public class MyTextWatcher implements TextWatcher {
    private EditText editText;

    // Pass the EditText instance to TextWatcher by constructor
    public MyTextWatcher(EditText editText) {
        this.editText = editText;
    }

    @Override
    public void afterTextChanged(Editable e) {
        // Unregister self before update
        editText.removeTextChangedListener(this);

        // The trick to update text smoothly.
        e.replace(0, e.length(), e.toString());

        // Re-register self after update
        editText.addTextChangedListener(this);
    }

    ...
}

科特林:

class MyTextWatcher(private val editText: EditText) : TextWatcher {

  override fun afterTextChanged(e: Editable) {
    editText.removeTextChangedListener(this)
    e.replace(0, e.length, e.toString())
    editText.addTextChangedListener(this)
  }

  ...
}

用法:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

如果您使用的是editText.setText()而不是editable.replace() ,則在快速輸入文本時可能會感到有點滯后。

修復的簡單技巧......只要您導出新編輯文本值的邏輯是冪等的(這可能是,但只是說)。 在您的偵聽器方法中,僅當當前值與上次修改值不同時才修改編輯文本。

例如,

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

您可以使用 Kotlin DSL 語法來獲得通用解決方案:

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

在您的 TextWatcher 中,您可以將其用作:

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

我用這種方式:

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

並且每次需要以編程方式更改文本時,請先清除焦點

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

這對我很有用

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

使用tag字段可以輕松解決該問題,您甚至不必處理 editText 的焦點。

以編程方式設置文本和標簽

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

檢查 onTextChanged 中的tag

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

如果您需要專注於EditText更改文本,您可以請求焦點:

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

這樣做很容易

editText.addTextChangedListener(object : TextWatcher {

        private var isEditing = false

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {


        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

        }

        override fun afterTextChanged(s: Editable?) {
            if(!isEditing){
                isEditing = true
                editText.setText("Hello World!")
                isEditing = false
            }

        }
    })

這樣它就不會無限循環

試試這個邏輯:我想 setText("") 而不是無限循環,這段代碼對我有用。 我希望您可以修改它以滿足您的要求

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

這是一個方便的類,它提供比 TextWatcher 更簡單的界面,用於希望在發生更改時查看更改的正常情況。 它還允許在 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());
            }
        }
    }
}

像這樣使用它:

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

每當您想修改editText的內容而不引起一系列遞歸編輯時,請執行以下操作:

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

我的變種:

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


}

僅使用 setOnTextChangeListener() 設置偵聽器並僅使用 setNewText 設置文本(我想覆蓋 setText(),但它是最終的)

我創建了一個抽象類,它減輕了通過 TextWatcher 修改 EditText 時的循環問題。

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

很簡單,用這個方法設置文本

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

我的解決方案與其他解決方案非常相似,只是它是我使用視圖綁定對其進行的自定義旋轉

我創建了以下 TextWatcher

class ControlledTextWatcher(
    private val parent: TextView,
    private val onChange: ((text: CharSequence?, start: Int, before: Int, count: Int) -> Unit)?,
    private val beforeChange: ((text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null,
    private val afterChange: ((editable: Editable?) -> Unit)? = null
) : TextWatcher {
    init {
        parent.addTextChangedListener(this)
    }

    private var enabled = true

    var text: String?
        get() = parent.value
        set(value) {
            this.enabled = false
            parent.text = value
            this.enabled = true
        }

    var res: Int
        get() = throw RuntimeException("String resource cannot be retrieved after being set")
        set(value) {
            parent.text = parent.context.getString(value)
        }


    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        if (enabled)
            beforeChange?.invoke(s, start, count, after)
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        if (enabled)
            onChange?.invoke(s, start, before, count)
    }

    override fun afterTextChanged(s: Editable?) {
        if (enabled)
            afterChange?.invoke(s)
    }

    fun detach() {
        parent.removeTextChangedListener(this)
    }
}

我主要將它用於像這樣的視圖綁定

class 測試活動:AppCompatActivity() {

class TestActivity : AppCompatActivity() {
    private lateinit var binding: ActivityTestBinding
    private val edit by lazy { ControlledTextWatcher(binding.text, this::textChanged }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

因此,當我希望對實際的EditText進行更改時,我使用ControlledTextWatchertextres屬性,如下所示:

edit.text = "hello world" //this does not trigger the text watcher

但是當用戶改變EditText時它會觸發

不幸的是,如果您想更改EditText的其他參數,則必須通過綁定獲取原始EditText或將這些函數復制到ControlledTextWatcher

afterChange中進行更改時也必須小心,因為更改已發布到TextView ,因此您可能會陷入無限循環

您應該確保文本更改的實現是穩定的,並且在不需要更改時不更改文本。 通常,這將是已經通過觀察者一次的任何內容。

最常見的錯誤是在關聯的 EditText 或 Editable 中設置新文本,即使文本實際上並未更改。

最重要的是,如果您對 Editable 而不是某些特定視圖進行更改,您可以輕松地重用您的觀察者,並且您還可以通過一些單元測試對其進行單獨測試,以確保它具有您想要的結果。

由於 Editable 是一個接口,你甚至可以使用它的一個虛擬實現,如果它的任何方法被調用試圖改變它的內容,當測試應該穩定的內容時,它會拋出一個 RuntimeException 。

我的做法是:

在寫段

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

聽者

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

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

        ....

無論如何都有效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM