如何在不触发Text Watcher的情况下更改EditText文本?


104

我有一个EditText带有客户文本监视程序的字段。在一段代码中,我需要更改使用的EditText中的值.setText("whatever")

问题是,一旦我做出更改, afterTextChanged就会调用该方法,从而创建了无限循环。如何在不触发afterTextChanged的情况下更改文本?

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

Answers:


70

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

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


你的提示对我来说足够了。实际上,我在onResume中注册了侦听器,但没有在onPause()中取消注册,因此它多次调用。
Smeet

有人可以解释吗?从技术上讲,我是android新手,需要更多详细信息,谢谢。
布迪·穆利约

116

简短答案

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

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

片段getActivity().getCurrentFocus()和科特林activity?.currentFocus
Mohammad Reza Khahani

@MohammadRezaKhahani嘿!你不需要那个 您可以hasFocus()直接在上致电EditText
威利·曼策尔

不理想。如果即使编辑文本具有焦点,我也想在没有触发侦听器的情况下设置setText()怎么办?
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);
    }
}

用法:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

如果您使用editText.setText()而不是editable.replace(),则在快速输入文本时可能会感到有些滞后。


为什么这被否决?这是我的问题的完美解决方案,而其他问题则没有解决。我将添加不必继承TextWatcher即可使此解决方案起作用。谢谢陈俊谦。
Michael Fulton

您取消注册和重新注册TextWatcher的想法很好。但是,虽然et.setText(“”)有效,但s.replace(0,s.length(),“”)无效。
stevehs17年

@ stevehs17,我真的不知道您的代码怎么样,但是用法已更新得非常详细,请尝试一下。
Chan Chun Him

15

容易解决的窍门... 只要您得出新编辑文本值的逻辑是幂等的(它可能是,但是只是说)。在您的侦听器方法中,仅在当前值与上次修改值不同时才修改编辑文本。

例如,

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
这仍然会导致方法被调用两次,但是,不是吗?
Trevor Hart

是的,这就是为什么我说它应该是幂等的(应该说得更清楚)……不是理想的选择,但是如果您担心单个方法调用不起作用的开销,则必须进行核心优化。如果是这样,您可以重新组织代码以在调用之前删除侦听器,然后在调用setText()之后重新添加。
Jeffrey Blattman

6

我用这种方式:

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

5

您可以使用Kotlin DSL语法为此提供通用解决方案:

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

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

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

清洁得多。Kotlin DSL很棒
Anjal Saneen

4

这对我有用

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

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

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

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

tag在onTextChanged中检查

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

3

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

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

1

尝试以下逻辑:我想在不进入无限循环的情况下使用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
        }
    });

1

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

0

我的变体:

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


0

我创建了一个抽象类,该类减轻了通过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);
}

0

非常简单,使用此方法设置文本

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

-2

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

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

最重要的是,如果您对Editable进行了更改,而不是对某些特定的View进行了更改,则可以轻松地重用观察者,还可以使用一些单元测试对其进行单独测试,以确保它具有所需的结果。

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


-2

我做这件事的方式:

在写段中

        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;

        ....

无论如何都可以。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.