[英]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
進行更改時,我使用ControlledTextWatcher
的text
或res
屬性,如下所示:
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.