Throttle onQueryTextChange in SearchView

2020-03-01 15:56发布

问题:

What's the best way to "throttle" onQueryTextChange so that my performSearch() method is called only once every second instead of every time the user types?

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

回答1:

Building on aherrick's code, I have a better solution. Instead of using a boolean 'canRun', declare a runnable variable and clear the callback queue on the handler each time the query text is changed. This is the code I ended up using:

@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:

I've come up to a solution using RxJava, particularly it's debounce operator.

With Jake Wharton's handy RxBinding, we'll have this:

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


回答3:

If you are using Kotlin and coroutines you could do the following:

var queryTextChangedJob: Job? = null

...

fun onQueryTextChange(query: String): Boolean {

    queryTextChangedJob?.cancel()

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


回答4:

You can easily achieve it with RxJava. Also, you will need RxAndroid and RxBinding (but you probably already have them in your project if you are using RxJava).

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

Here the full example by Kaushik Gopal.



回答5:

1) Create abstract 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 onDelayerQueryTextChange(String query);
}

2) Set it like this:

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


回答6:

I ended up with a solution similar to below. That way it should fire once every half second.

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