知道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() }
如果在时间间隔内,消息还没有发送出去,先判断是否有消息,确定是否在时间间隔内。
如果超过了间隔,再次设置间隔时间,并响应回调。