在 SearchView 中限制 onQueryTextChange

Throttle onQueryTextChange in SearchView

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

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

您可以使用 RxJava. Also, you will need RxAndroid and RxBinding 轻松实现它(但如果您使用的是 RxJava,您的项目中可能已经有了它们)。

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

Here Kaushik Gopal 的完整示例。

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

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

基于 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;
}

我想出了一个使用 RxJava, particularly it's 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`
            }
        });

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

var queryTextChangedJob: Job? = null

...

fun onQueryTextChange(query: String) {

    queryTextChangedJob?.cancel()
    
    queryTextChangedJob = launch(Dispatchers.Main) {
        delay(500)
        performSearch(query)
    }
}
  1. 创建摘要class:

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

柯林

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) {
        //...
}

使用协程和流程:

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