【问题标题】:Throttle onQueryTextChange in SearchView在 SearchView 中限制 onQueryTextChange
【发布时间】:2016-01-22 20:10:46
【问题描述】:

“限制”onQueryTextChange 的最佳方法是什么,以便我的 performSearch() 方法每秒只调用一次,而不是每次用户键入时调用?

public boolean onQueryTextChange(final String newText) {
    if (newText.length() > 3) {
        // throttle to call performSearch once every second
        performSearch(nextText);
    }
    return false;
}

【问题讨论】:

    标签: java android search searchview android-search


    【解决方案1】:

    基于 aherrick 的代码,我有一个更好的解决方案。每次更改查询文本时,声明一个可运行变量并清除处理程序上的回调队列,而不是使用布尔值“canRun”。这是我最终使用的代码:

    @Override
    public boolean onQueryTextChange(final String newText) {
        searchText = newText;
    
        // Remove all previous callbacks.
        handler.removeCallbacks(runnable);
    
        runnable = new Runnable() {
            @Override
            public void run() {
                // Your code here.
            }
        };
        handler.postDelayed(runnable, 500);
    
        return false;
    }
    

    【讨论】:

      【解决方案2】:

      如果您使用 Kotlin 和协程,您可以执行以下操作:

      var queryTextChangedJob: Job? = null
      
      ...
      
      fun onQueryTextChange(query: String) {
      
          queryTextChangedJob?.cancel()
          
          queryTextChangedJob = launch(Dispatchers.Main) {
              delay(500)
              performSearch(query)
          }
      }
      

      【讨论】:

      • 协程是前进的方向!感谢您的回答。
      • 谢谢@jc12,我的代码大部分都减少了。
      【解决方案3】:

      我想出了一个使用RxJava 的解决方案,尤其是debounce 运算符。

      有了 Jake Wharton 的方便 RxBinding,我们将拥有这个:

      RxSearchView.queryTextChanges(searchView)
              .debounce(1, TimeUnit.SECONDS) // stream will go down after 1 second inactivity of user
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe(new Consumer<CharSequence>() {
                  @Override
                  public void accept(@NonNull CharSequence charSequence) throws Exception {
                      // perform necessary operation with `charSequence`
                  }
              });
      

      【讨论】:

        【解决方案4】:
        1. 创建抽象类:

          public abstract class DelayedOnQueryTextListener implements SearchView.OnQueryTextListener {
          
           private Handler handler = new Handler();
           private Runnable runnable;
          
           @Override
           public boolean onQueryTextSubmit(String s) {
               return false;
           }
          
           @Override
           public boolean onQueryTextChange(String s) {
               handler.removeCallbacks(runnable);
               runnable = () -> onDelayerQueryTextChange(s);
               handler.postDelayed(runnable, 400);
               return true;
           }
          
           public abstract void onDelayedQueryTextChange(String query);
          

          }

        2. 这样设置:

          searchView.setOnQueryTextListener(new DelayedOnQueryTextListener() {
               @Override
               public void onDelayedQueryTextChange(String query) {
                   // Handle query
               }
           });
          

        【讨论】:

          【解决方案5】:

          为了科林

          如果是coroutineScope.launch(Dispatchers.Main) {},您可能会遇到问题Suspend function '...' should be called only from a coroutine or another suspend function

          我找到了以下方法

          private var queryTextChangedJob: Job? = null
          private lateinit var searchText: String
          

          接下来别忘了使用implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05"

          override fun onQueryTextChange(newText: String?): Boolean {
          
              val text = newText ?: return false
              searchText = text
          
              queryTextChangedJob?.cancel()
              queryTextChangedJob = lifecycleScope.launch(Dispatchers.Main) {
                  println("async work started...")
                  delay(2000)
                  doSearch()
                  println("async work done!")
              }
          
              return false
          }
          

          如果您想在 ViewModel 中使用 launch,请使用 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha05"

          queryTextChangedJob = viewModelScope.launch(Dispatchers.Main) {
                  //...
          }
          

          【讨论】:

            【解决方案6】:

            您可以使用RxJava 轻松实现它。此外,您将需要 RxAndroidRxBinding(但如果您使用 RxJava,您的项目中可能已经拥有它们)。

            RxTextView.textChangeEvents(yourEditText)
                      .debounce(1, TimeUnit.SECONDS)
                      .observeOn(AndroidSchedulers.mainThread())
                      .subscribe(performSearch());
            

            Here Kaushik Gopal 的完整示例。

            【讨论】:

            • 谢谢!但是,此时我对引入 RxJava 并不是很感兴趣。可以原生完成吗?
            • 不幸的是,我不知道任何常见的干净解决方案来解决这个问题。我绝对可以通过 Handler 延迟或 Timer 和 TimerTasks 来实现。
            【解决方案7】:

            使用协程和流程:

            private fun SearchView.onQueryTextChanged(): ReceiveChannel<String> =
                Channel<String>(capacity = Channel.UNLIMITED).also { channel ->
                    setOnQueryTextListener(object : SearchView.OnQueryTextListener{
                        override fun onQueryTextSubmit(query: String?): Boolean {
                            return false
                        }
            
                        override fun onQueryTextChange(newText: String?): Boolean {
                            newText.orEmpty().let(channel::offer)
                            return true
                        }
                    })
                }
            
            @ExperimentalCoroutinesApi
            fun <T> ReceiveChannel<T>.debounce(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, scope: CoroutineScope): ReceiveChannel<T> =
                Channel<T>(capacity = Channel.CONFLATED).also { channel ->
                    scope.launch {
                        var value = receive()
                        whileSelect {
                            onTimeout(time) {
                                channel.offer(value)
                                value = receive()
                                true
                            }
                            onReceive {
                                value = it
                                true
                            }
                        }
                    }
                }
            

            并像这样添加到您的搜索视图中:

            lifecycleScope.launch {
               searchView.onQueryTextChanged().debounce(time = 500, scope = lifecycleScope).consumeEach { newText ->
                      //Use debounced query
               }
            }
            

            【讨论】:

              【解决方案8】:

              我最终得到了类似于下面的解决方案。这样它应该每半秒触发一次。

                      public boolean onQueryTextChange(final String newText) {
              
                          if (newText.length() > 3) {
              
                              if (canRun) {
                                  canRun = false;
                                  handler.postDelayed(new Runnable() {
                                      @Override
                                      public void run() {
              
                                          canRun = true;
                                          handleLocationSearch(newText);
                                      }
                                  }, 500);
                              }
                          }
              
                          return false;
                      }
              

              【讨论】:

              • 这是一个错误的解决方案。请参阅proandroiddev.com/…
              • 此解决方案缺少取消。如果不再需要观察查询文本的变化,请记住从处理程序中删除回调
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-06-25
              • 1970-01-01
              • 2015-07-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多