【问题标题】:How can I change the EditText text without triggering the Text Watcher?如何在不触发 Text Watcher 的情况下更改 EditText 文本?
【发布时间】:2012-03-12 04:58:41
【问题描述】:

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

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

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

【问题讨论】:

    标签: java android event-handling infinite-loop


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      例如,

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

      【讨论】:

      • 这仍然会导致方法被调用两次,不是吗?
      • 是的,这就是为什么我说它应该是幂等的(应该更清楚)......不理想,但如果你担心单个的开销,你必须真正进行核心优化方法调用不起作用。如果是这样,您可以重新组织代码以在侦听器调用 setText() 之前删除它,然后重新添加它。
      【解决方案3】:

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

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

      最重要的是,如果您对 Editable 而不是某些特定的 View 进行更改,您可以轻松地重用您的 watcher,并且您可以通过一些单元测试对其进行隔离测试,以确保它具有您想要的结果。

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

      【讨论】:

        【解决方案4】:

        我的变种:

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

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

            简答

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

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

            【讨论】:

            • 不理想。即使edittext有焦点,如果我想在没有触发监听器的情况下设置setText()怎么办?
            【解决方案7】:

            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 子类化这个解决方案。谢谢陈俊谦。
            • 您注销和重新注册 TextWatcher 的想法非常有效。但是,虽然 et.setText("") 有效,但 s.replace(0, s.length(), "") 无效。
            • @stevehs17,我真的不知道你的代码怎么样,但是用法已经更新得很详细了,试试看。
            【解决方案8】:

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

            【讨论】:

              【解决方案9】:

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

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

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  【解决方案11】:

                  我就是这样用的:

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

                  【讨论】:

                    【解决方案12】:

                    您可以使用 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 很棒
                    • 就是这样。谢谢。
                    【解决方案13】:

                    这对我很有用

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

                    【讨论】:

                      【解决方案14】:

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

                      【讨论】:

                      • 这是个好办法!非常感谢!
                      【解决方案15】:

                      我的做法:

                      在写段

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

                      不管怎样都可以。

                      【讨论】:

                        【解决方案16】:

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

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

                        【讨论】:

                          【解决方案17】:

                          这样做很简单

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

                          这样就不会陷入无限循环

                          【讨论】:

                            【解决方案18】:

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

                            我创建了以下 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)
                                }
                            }
                            

                            我主要将它用于像这样的视图绑定

                            类 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,因此您可能会陷入无限循环

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2022-07-23
                              • 2014-01-16
                              • 1970-01-01
                              相关资源
                              最近更新 更多