RxJava|使用RxJava优化EditText#onTextChanged回调
文章图片
onTextChanged 【RxJava|使用RxJava优化EditText#onTextChanged回调】EditText是常用的文字输入控件,但是其回调接口设计的不友好,需要实现三个接口,而大多数场景我只关心onTextChanged
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
Log.d(TAG, "query: ${s}")
}
})
而即使是最关心的
onTextChanged
,由于其语义太过简单(每次发生变化就会回调),在某些业务场景不能达到预期效果,例如常见的实时搜索
场景,希望能够在输入框中输入关键字时进行实时的联想或搜索,此时使用EditText#onTextChanged
的话会遇到很多问题:多次回调 例如,上面代码中,依次输入
a b c d e
,得到以下日志query: a
query: ab
query: ab
query: abc
query: abcd
query: abcd
其中ab和abcd出现了两次。经调查,与EditText的设置有关
android – TextWatcher events are being fired multiple times – Stack Overflow
当然,这个问题并非值出现在实时搜索这个场景,其也反映出了这个控件是有多坑
频繁触发 虽然希望输入的反馈能够有
实时
的效果,但又不希望太敏锐(人类真难伺候|||),当快速输入一个组合时,例如“a” “aa” “aaa” “aaaa”,说明我们的目的性很强,只对最后的
”aaaa“`出结果就好了异步结果不正确 由于输入后出发了一个异步调用,那么有可能连续多次异步请求的结果回调时机不符合预期,例如 输入
“a” “aa”
,触发两个异步请求,但是有可能“a”的结果后返回,而输入框中已经停留到了“aa”状态。使用RxJava优化 以上种种问题,都是由于
EditText#onTextChanged
的回调语义不能满足预期业务场景所致,此时可以使用RxJava对其进行优化回调多次
val queryPublisher = PublishSubject.create>()editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
queryPublisher.onNext(s.toString())
}
})queryPublisher.distinctUntilChanged().subscribe({
Log.d(TAG, "query: ${it}")
})
PublishSubject
提供了一个Stream的管道,接受onTextChanged
的原始回调,distinctUntilChanged
用来保证每个value只回调一次query: a
query: ab
query: abc
query: abcd
频发触发
queryPublisher.distinctUntilChanged()
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
Log.d(TAG, "query: ${it}")
})
使用
debounce
添加防抖。需要注意的是throttleLast
虽然也是类似功能的操作符,但效果上不符合本需求的预期,下面对比一下两者的不同:- debounce
发送数据后一段时间内,没有新数据,则把这个数据真正发送出去;如果在这段时间内有新的数据发送,则以这个数据作为将要发送的数据项,并重新计时。
文章图片
- throttleLast
将每个时间段内最后一个数据进行发送。用户在连续输入的中途可能会发生结果请求,不符合预期。
文章图片
dipose
private var searchDisposable: Disposable? = nulloverride fun onCreate(savedInstanceState: Bundle?) {
// 略...queryPublisher.distinctUntilChanged()
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
Log.d(TAG, "query: ${it}")
searchDisposable?.dispose()
searchDisposable = search(query = it)
.subscribe({
// 搜索结果显示
})
})
}/**
* 结果请求
*/
fun search(query: String?): Observable> {
// 略...
return Observable.empty>()
}
到得到新的输入时,将前一次异步请求手动停止
switchMap
switchMap
操作符相对于dispose
的方式更加优雅RxJava also implements the switchMap operator. It behaves much like flatMap, except that whenever a new item is emitted by the source Observable, it will unsubscribe to and stop mirroring the Observable that was generated from the previously-emitted item, and begin only mirroring the current one.
switchMap
与flatMap
类似,也可用来进行Stream的切换,但当switchMap切换新Stream时,旧stream会自动停止。通过和flatMap的对比,体会一下其含义:- flatMap
文章图片
- switchMap
文章图片
override fun onCreate(savedInstanceState: Bundle?) {
// 略...queryPublisher.distinctUntilChanged()
.debounce(500, TimeUnit.MILLISECONDS)
.switchMap { search(it) }
.subscribe({
// 搜索结果显示
Log.d(TAG, "result: ${it}")
})
}
推荐阅读
- 由浅入深理解AOP
- 【译】20个更有效地使用谷歌搜索的技巧
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- iOS中的Block
- Linux下面如何查看tomcat已经使用多少线程
- 使用composer自动加载类文件
- android|android studio中ndk的使用
- 使用协程爬取网页,计算网页数据大小