知道handle的原理,但是你会使用吗

知道handle的原理,但是你会使用吗

开发中常常遇到的需求,根据搜索框键入的关键字发起搜索,如果监听输入框的文字变化直接发起请求,会频繁调用接口,也会请求非用户想要的结果。比如天冷了,小明想买羽绒服,输入 ‘羽’,就会发起 ‘羽’ 关键字的请求,可能搜到羽毛球羽毛等无关内容,再次键入羽绒,会搜到羽绒服羽绒被羽绒棉。而小明只想要羽绒服,所以优雅的做法是监听输入完成才发起请求,但是每一次键入都会触发 TextWatcher 回调,为了实现监听完成,我们需要一个防抖函数。

如果写过前端,对节流和防抖再熟悉不过了。网页的监听,输入的变化,都用得到。

防抖函数

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

js 的实现:

var timer; // 维护同一个timer
function debounce(fn, delay) {
    clearTimeout(timer);
    timer = setTimeout(function(){
        fn();
    }, delay);
}

那么 Android 中怎么理解和使用呢?小明买羽绒服的需求,在输入停顿的时间,发起请求,为了及时性,这个时间不易过长,假如500毫秒,那么就是在 TextWatcher 的 onTextChanged 回调后,500毫秒内不再输入,那么就发起请求。

需求明白了,需要一个间隔时间,还有一个回调。然后如果在间隔时间内,就删除回调,重新计时,直到间隔时间内不再触发,说明输入完成,可以响应回调了。

class Debounce(
    //间隔时间
    private val timeout: Long,
    //回调
    private val callback: () -> Unit

) {
    private val handler = Handler(Looper.getMainLooper())
    private val runnable = Runnable(callback)

    //触发防抖
    fun process() {
        handler.removeCallbacks(runnable)
        handler.postDelayed(runnable, timeout)
    }
}

因为 TextWatcher 的接口有三个,而我们只需要 onTextChanged ,所以写一个扩展函数:

fun EditText.onChange(cb: (String) -> Unit) {
    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            cb(s.toString())
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    })
}

使用:

        val debounce = Debounce(500) {
            Log.e("MainActivity", "onCreate(MainActivity.java:13==${editText.text.toString()})")
        }
        editText.onChange { text ->
            debounce.process()
        }

连续输入,并不会触发打印。这里要注意的是,不能直接使用handler.postDelayed(callback,timeout),这样就 removeCallbacks 就失效了。因为不是一个 Runnable了。

如果想把变化后的 text 传进去,直接使用,我们可以换一种写法:

const val MESSAGE_WHAT = 1

class Debouncer(
    private val timeout: Long,
    private val callback: (String) -> Unit

) {
    private val handler = Handler(Looper.getMainLooper()) { message ->
        if (message.what != MESSAGE_WHAT) {
            return@Handler false
        }
        callback(message.obj as String)
        true
    }

    fun process(text: String) {
        handler.removeMessages(MESSAGE_WHAT)
        val message = handler.obtainMessage(MESSAGE_WHAT, text)
        handler.sendMessageDelayed(message, timeout)
    }
}

使用:

        val debouncer = Debouncer(500) { text ->
            Log.e("MainActivity", "onCreate(MainActivity.java:13==$text)")
        }

        editText.onChange { text ->
            debouncer.process(text)
        }

节流函数

每隔一段时间,只执行一次函数。

节流函数我们遇到的最多,比如点击事件,防止连续点击,我们通常会去禁止多次点击。可以用 rxbind、计时、aop等,也可以用 handle 实现,这当然不是推荐的做法。

class Throttler(
    private val timeout: Long,
    private val callback: () -> Unit
) {
    private val handler = Handler(Looper.getMainLooper())
    fun onAction() {
        if (handler.hasMessages(MESSAGE_WHAT)) {
            return
        }
        val message = handler.obtainMessage(MESSAGE_WHAT)
        handler.sendMessageDelayed(message, timeout)
        callback()
    }
}

使用:

        val throttler = Throttler(1000) {
            Log.e("MainActivity", "onCreate(MainActivity.java:22==${System.currentTimeMillis()})")
        }
        button.setOnClickListener { throttler.onAction() }

如果在时间间隔内,消息还没有发送出去,先判断是否有消息,确定是否在时间间隔内。

如果超过了间隔,再次设置间隔时间,并响应回调。

你可能感兴趣的:(知道handle的原理,但是你会使用吗)