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

发布于 2025-01-07 23:24:44 字数 288 浏览 1 评论 0原文

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

问题是,一旦我进行更改,afterTextChanged 方法就会被调用,这会创建一个无限循环。如何更改文本而不触发 afterTextChanged?

我需要 afterTextChanged 方法中的文本,因此不建议删除 TextWatcher

I have an EditText field with a Customer Text Watcher on it. In a piece of code I need to change the value in the EditText which I do using .setText("whatever").

The problem is as soon as I make that change the afterTextChanged method gets called which created an infinite loop. How can I change the text without it triggering afterTextChanged?

I need the text in the afterTextChanged method so don't suggest removing the TextWatcher.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

香橙ぽ 2025-01-14 23:24:44

简短回答

您可以检查当前哪个视图具有焦点,以区分用户触发的事件和程序触发的事件。

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

Short answer

You can check which View currently has the focus to distinguish between user and program triggered events.

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

    //...
});

Long answer

As an addition to the short answer:
In case myEditText already has the focus when you programmatically change the text you should call clearFocus(), then you call setText(...) and after you you re-request the focus. It would be a good idea to put that in a utility function:

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

For Kotlin:

Since Kotlin supports extension functions your utility function could look like this:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}
り繁华旳梦境 2025-01-14 23:24:44

您可以取消注册观察者,然后重新注册。

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

You could unregister the watcher, and then re-register it.

Alternatively, you could set a flag so that your watcher knows when you have just changed the text yourself (and therefore should ignore it).

花落人断肠 2025-01-14 23:24:44

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

    ...
}

Kotlin:

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(),则在快速输入文本时可能会感到有点滞后。

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

    ...
}

Kotlin:

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

  ...
}

Usage:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

You may feel a little bit lag when entering text rapidly if you are using editText.setText() instead of editable.replace().

凤舞天涯 2025-01-14 23:24:44

修复的简单技巧...只要派生新编辑文本值的逻辑是幂等的(它可能是,但只是说)。在侦听器方法中,仅当当前值与上次修改值不同时才修改编辑文本。

例如,

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

Easy trick to fix ... as long a your logic to derive the new edit text value is idempotent (which it probably would be, but just saying). In your listener method, only modify the edit text if the current value is different than the last time you modified the value.

e.g.,

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);
    }
  }
};
℡Ms空城旧梦 2025-01-14 23:24:44

您可以使用 Kotlin DSL 语法来获得通用解决方案:

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

在 TextWatcher 中,您可以将其用作:

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

You can use Kotlin DSL syntax to have the generic solution for this:

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

And inside your TextWatcher, you can use it as:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}
吻安 2025-01-14 23:24:44

我这样使用:

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

I use that way:

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

And every time you need to change text programatically, first clear the focus

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);
烙印 2025-01-14 23:24:44

使用 tag 提交可以轻松解决该问题,您甚至不必处理 editText 的焦点。

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

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

检查 onTextChanged 中的标签

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

The problem can be easily solved using tag filed and you don't even have to deal with editText's focus.

Setting the text and the tag programmatically

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

Checking for the tag in onTextChanged

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    if (editText.tag == null) {
       // your code
    }
}
永不分离 2025-01-14 23:24:44

这对我来说很有用

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

This works good for me

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) { }
    });
爱殇璃 2025-01-14 23:24:44

如果您需要专注于 EditText 更改文本,您可以请求焦点:

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

If you need to stay focused on EditText change text you could request focus:

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}
吻泪 2025-01-14 23:24:44

很简单,这样做

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
            }

        }
    })

就不会无限循环

It's easy just do it like this

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
            }

        }
    })

in this way it don't stock in infinite loop

青衫儰鉨ミ守葔 2025-01-14 23:24:44

试试这个逻辑:
我想 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
        }
    });

try this logic:
I wanted to setText("") without going to infinite loop and this code works for me. I hope you can modify this to fit your requirement

        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
        }
    });
少女情怀诗 2025-01-14 23:24:44

这是一个方便的类,它提供了比 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

Here's a handy class that provides a simpler interface than TextWatcher for the normal case of wanting to see changes as they occur. It also allows for ignoring the next change as the OP requested.

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

Use it like this:

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

Whenever you want to modify the contents of editText without causing a cascade of recursive edits, do this:

listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener
×纯※雪 2025-01-14 23:24:44

我的变体:

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(),但它是最终的)

My variant:

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


}

Set listeners only using setOnTextChangeListener() and set text only using setNewText (I wanted to override setText(), but it is final)

神经暖 2025-01-14 23:24:44

我创建了一个抽象类,它可以缓解通过 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);
}

I've created an abstract class which mitigates the cyclic issue of when a modification to the EditText is made via a 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);
}
给不了的爱 2025-01-14 23:24:44

很简单,用这个方法设置文本

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

Very simple, set text with this method

void updateText(EditText et, String text) {
   if (!et.getText().toString().equals(text))
       et.setText(text);
}
陌路终见情 2025-01-14 23:24:44

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

我创建了以下 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 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 中,因此您可以最终陷入无限循环

My solution for this is a lot like the others only it's my custom spin on it using viewbindings

I created the following 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)
    }
}

and I use it mainly with view bindings like so

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

so when I wish to make changes to the actual EditText I use the text or res attribute of the ControlledTextWatcher like so:

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

but when the user alters the EditText it will trigger

unfortunatelly with this solution if you want to alter other parameters of the EditText, you either have to get the original EditText through bindings or copy those functions to the ControlledTextWatcher

also you have to be careful when making changes in afterChange because the change is posted to the TextView so you may end up with an endless loop

长亭外,古道边 2025-01-14 23:24:44

您应该确保文本更改的实现是稳定的,并且如果不需要更改,则不会更改文本。通常,这将是观察者已经浏览过一次的任何内容。

最常见的错误是在关联的 EditText 或 Editable 中设置新文本,即使文本实际上并未更改。

最重要的是,如果您对可编辑而不是某些特定视图进行更改,您可以轻松地重新使用您的观察者,并且您还可以通过一些单元测试单独测试它,以确保它具有您想要的结果。

由于 Editable 是一个接口,您甚至可以使用它的虚拟实现,如果在测试应该稳定的内容时调用其任何方法来尝试更改其内容,则该实现会抛出 RuntimeException。

You should ensure your implementation of text changes is stable and does not change the text if no change is needed. Normally that would be any content that's already been through the watcher once.

The most common mistake is to set a new text in the associated EditText or the Editable even though the text was not actually changes.

On top of that, if you make your changes to the Editable instead of some specific View, you can easily resuse your watcher, and also you can test it in isolation with some unit tests to ensure it has the outcome you want.

Since Editable is an interface you could even use a dummy implementation of it that throws a RuntimeException if any of its methods are called that try to change its contents, when testing content that should be stable.

弄潮 2025-01-14 23:24:44

我的做法是:

在写入段中,

        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;

        ....

无论如何都可以工作。

My way to do the thing:

In the write segment

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

The listener

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

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

        ....

Works anyway.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文