如何在不触发文本观察器的情况下更改编辑文本



我有一个EditText字段,上面有一个客户文本观察器。在一段代码中,我需要更改编辑文本中的值,我使用 .setText("whatever") .

问题是一旦我进行了更改,就会调用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:

由于 Kotlin 支持扩展函数,因此您的实用程序函数可能如下所示:

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

您可以取消注册观察程序,然后重新注册它。

或者,您可以设置一个标志,以便观察者知道您刚刚自己更改了文本(因此应忽略它(。

Java:

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

在文本观察器中,您可以将其用作:

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

这个问题可以使用tag归档轻松解决,你甚至不必处理 editText 的重点。

以编程方式设置文本和标记

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

检查文本更改中的tag

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

我有好处

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

如果您需要专注于更改文本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
            }
        }
    })

这样它就不会在无限循环中库存

试试这个逻辑:我想在不进行无限循环的情况下设置Text("(,这段代码对我有用。我希望您可以修改它以满足您的要求

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

我的解决方案与其他解决方案非常相似,只是它是我使用视图绑定对其进行的自定义旋转

我创建了以下文本观察器

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

我主要将其与这样的视图绑定一起使用

类 TestActivity : 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 是一个接口,您甚至可以使用它的虚拟实现,如果在测试应该稳定的内容时调用了尝试更改其内容的任何方法,则会抛出 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;
        ....

无论如何都可以。

相关内容

  • 没有找到相关文章

最新更新