文本观察器事件被多次触发

56

我在使用TextWatcher时遇到了一个烦人的问题。我已经在网上搜索了很久,但是没有找到任何有用的信息。如果有人能帮忙解决这个问题,我将不胜感激。

由于某些原因,每次文本更改时对TextWatcher事件的调用是不稳定的。有时它们会被触发一次(就像应该的那样),有时两次,有时三次。我不知道为什么,整个过程非常简单明了。此外,有时在afterTextChanged()中Editable参数返回的toString()和length()都是空值。

以下是代码:

    private TextWatcher mSearchAddressTextChangeListener = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) { }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

        @Override
        public void afterTextChanged(Editable searchedAddress) {
           System.out.println("called multiple times.");   
        }
    };

afterTextChanged()(以及AsyncTask)中,我没有对文本或EditText视图进行任何更改。

我看到了在Events of TextWatcher are being called twice中提出的问题,但我的事件被触发的次数比两次多(或少)。

无论如何,感激任何帮助。

编辑:我删除了afterTextChanged()的内容,因为即使没有我的代码也会出现这个问题。这让我相信这是一个错误。当在普通字符后输入一个空格字符时(事件处理程序被触发两次),或者在删除普通字符后移除一个空格字符时(退格。事件处理程序被触发3次),就会出现错误。仍然需要帮助。


1
@deadfish 适配器并不是真正的问题。它只是一个基于 TextWatcher 输入的文本填充的列表。问题,就像我说的那样,是 TextWatcher 监听器上事件被调用的次数。对我来说,这完全是随机的。 - AsafK
1
是的,但是每次更改与TextWatcher连接的视图(更改文本状态)时,都会调用TextWatcher,你明白我的意思吗? - deadfish
@Akash 不是这样的。看起来像是一个 bug,因为即使我删除了 afterTextChanged() 方法内部的代码,它仍然会发生。如果你找到了解决方案,请告诉我。 - AsafK
2
你注册了多少次相同的监听器?因为当你使用addTextChangedListener()方法时,它会将每个监听器实例保存在列表中,当文本监听器需要被通知时,它会遍历监听器列表。 - Nikola Despotoski
@AsafK 我尝试按照你说的方式(4.0,SDK构建)简单地重现了这个问题,但我的观察器被正常调用,就像它应该做的那样。请看这里http://pastebin.com/0UTcUnGa - Nikola Despotoski
显示剩余10条评论
10个回答

62

当我在持续文本的末尾位置使用光标按下退格键时,我遇到了同样的问题。此时onTextChange会被调用3次: - 第一次是正确的s值 - 第二次是空值 - 第三次又是正确的值

在网上搜索后,我尝试将EditText的inputType更改为

android:inputType="textNoSuggestions"

不要问我为什么,但它起作用了,现在afterTextChanged只被调用一次。


1
这是因为当用户输入一个空格时,某些设备将会把前面的单词大写(如果它是第一个单词且 inputType="name")。因此,afterTextChanged 会被调用两次。一次是针对空格,另一次是针对大写化。 - Tamby Kojak
1
这适用于遵循此输入类型的键盘。对于需要建议栏(即日语)的外语,它仍将调用该方法两次。 - Dave Chen
遇到了相同的问题,直到现在API 27华硕 - Budi Mulyo
请注意,android:inputType="number"也可以像我这种情况下需要“货币”输入一样工作。 - Aerim

10
boolean isOnTextChanged = false;

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    isOnTextChanged = true;
}

@Override
public void afterTextChanged(Editable quantity) {
    if (isOnTextChanged) {
        isOnTextChanged = false;
       //dosomething
    }

1
欢迎velraj - 请添加一些解释您代码逻辑的文本 - 这将有助于原始帖子的其他人理解。请阅读如何回答 - https://stackoverflow.com/help/how-to-answer。请解释它与usman的答案有何不同,例如。 - micstr

8
根据TextWatcher的开发者页面,如果在TextWatcher中对Editable进行更改,则会触发进一步调用链接到该Editable的所有TextWatcher。现在,显然你的代码不会触发这种行为。
但是,很可能系统中有一个TextWatcherEditable相关联,出于任何原因,您描述的情况可能发生。我听到你哭喊着问:“为什么?”
首先,经典的防御:没有理由不发生,并且严格地说,应该编写应用程序代码以使其具有弹性。
第二,虽然我无法证明它,但我可以想象处理EditText中显示文本布局的代码使用TextWatcher来处理更新屏幕上的文本显示。此代码可能会将控制代码(未显示)插入到Editable中,以确保良好的换行等。它甚至可能会循环几次才能正确执行,您可能只在它完成所有操作后才收到第一个调用...
编辑
根据@Learn OpenGL ES的评论,对于自动更正之类的事情,调用TextWatcher会是正常的。

1
看起来是自动更正;关闭它可以修复奇怪的回调。 - Learn OpenGL ES

6
您可以使用布尔检查,例如:

u可以使用布尔检查,例如:

    inputBoxNumberEt.addTextChangedListener(new TextWatcher() {

        boolean ignoreChange = false;

        @Override
        public void afterTextChanged(Editable s) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start,
                                      int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {
            if (!ignoreChange) {
              ///Do your checks                    
                ignoreChange = true;
                inputBoxNumberEt.setText(string);
                inputBoxNumberEt.setSelection(inputBoxNumberEt.getText().length());
                ignoreChange = false;
            }
        }
    });

4

我尝试了所有在这个问题上回答的解决方案,但都没有对我起作用。但是经过一些搜索,我找到了这篇文章

使用RxJava使防抖动效果很好。以下是我的最终解决方案:

在Gradle文件中添加RxJava依赖项:

compile 'io.reactivex:rxandroid:1.0.1'
compile 'io.reactivex:rxjava:1.0.14'
compile 'com.artemzin.rxjava:proguard-rules:1.0.14.2'

实现你的主题:

PublishSubject<String> yourSubject = PublishSubject.create();
    yourSubject .debounce(100, TimeUnit.MILLISECONDS)
            .onBackpressureLatest()
            .subscribe(s -> {
                //Implements anything you want
            });

在TextWatcher中使用你的主题:

TextWatcher myTextWatcher = 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) {
        yourSubject.onNext(s.toString()); //apply here your subject
    }

    @Override
    public void afterTextChanged(Editable s) {
    }
};

在EditText监听器中添加TextWatcher:
my_edit_text.addTextChangedListener(myTextWatcher);

解决方案是否处理退格键? - Michael Osofsky
是的,字符串中的任何更改都会调用TextWatcher。 - Ângelo Polotto
1
防抖,这么漂亮的名字,是“这个 bug 是从哪里来的?”的代名词。 - Maurício Giordano

2
对我来说,下面的代码有效。请注意,在afterTextChanged方法中的if条件循环后,布尔值已被更改。"Original Answer"翻译成"最初的回答"。
        edittext.addTextChangedListener(new TextWatcher() 
        {
            boolean considerChange = false;

            public void beforeTextChanged(CharSequence cs, int start, int count, int after) {}

            public void onTextChanged(CharSequence cs, int start, int before, int count) {}

            public void afterTextChanged(Editable editable) 
            {
                if (considerChange) 
                { 
                    // your code here
                }
                considerChange = !considerChange; //see that boolean value is being changed after if loop
            }


        });

0

或者,你可以使用其他方式代替TextWatcher

editTxtView.doOnTextChanged { text, start, before, count ->  }
editTxtView.doAfterTextChanged {  }

这只是在底层使用TextWatcher,并且存在相同的问题。 - Sam

0

假设每个字符的打字速度为300毫秒。

    private var textDispatcherHandler = Handler(Looper.getMainLooper())
    private var textDispatcherRunnable:Runnable? = null

    override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        if(p0!=null){
            dispatchTextToFilter(p0.toString())
        }
    }
    
    private fun dispatchTextToFilter(string: String){
        if(textDispatcherRunnable!=null){
            textDispatcherHandler.removeCallbacks(textDispatcherRunnable!!)
        }
    
        if(textDispatcherRunnable==null){
            textDispatcherRunnable = Runnable {
                //doSomething here
            }
        }
        textDispatcherHandler.postDelayed(textDispatcherRunnable!!,300)
    }

0

我的问题与此有关,因为我正在模拟器中运行应用程序并通过笔记本电脑键盘输入(而非使用模拟器的键盘),会发生奇怪的重复输入。

在实体设备上不会发生这种情况。

根据Nico's 上面的答案得到一些启示,以下是对我有效的文本更改触发器只被触发一次的方法。

添加以下输入类型配置之一:

1)通过样式

<!-- in a style -->
<style name="My.Text.Style">
 <item name="android:inputType">text|textMultiLine|textVisiblePassword</item>
</style>

然后在XML中使用:

<EditText
   android:id="@+id/my_edit_text"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   style="@style/My.Text.Style"
   />

或者

2) 直接写入 XML

<EditText
   android:id="@+id/my_edit_text"
   android:inputType="text|textMultiLine|textVisiblePassword"
   />

两种方法都可以,这种风格只是为了在其他屏幕上重复使用而设计的。


0

请仔细检查代码,您已经多次添加了TextWatcher。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接