繁体   English   中英

在 SearchView 中限制 onQueryTextChange

[英]Throttle onQueryTextChange in SearchView

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

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

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

var queryTextChangedJob: Job? = null

...

fun onQueryTextChange(query: String) {

    queryTextChangedJob?.cancel()
    
    queryTextChangedJob = launch(Dispatchers.Main) {
        delay(500)
        performSearch(query)
    }
}

基于 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的解决方案,特别是它的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`
            }
        });
  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 } });

对于科林

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

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

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

是 Kaushik Gopal 的完整示例。

使用协程和流程:

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

我没有找到使我能够使用诸如distinctUntilChanged之类的流运算符的解决方案,我尝试了这种方法,也许有人需要它:

首先从SearchView创建一个扩展函数

private fun SearchView.getQueryTextChangeStateFlow(): StateFlow<String> {
    val query = MutableStateFlow("")
    
    setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            return true
        }
        override fun onQueryTextChange(newText: String): Boolean {
            query.value = newText
            return true
        }
    })

    return query
}

然后

 private fun setUpSearchStateFlow(searchView: SearchView) {
    lifecycleScope.launch {
        searchView.getQueryTextChangeStateFlow()
            .debounce(300)
            .distinctUntilChanged()
            .map { query ->
                startSearchRequestHere(query)
            }
            .flowOn(Dispatchers.Default)
            .collect {}
    }
}

参考

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM