使用RxJava优化EditText#onTextChanged回调

14 篇文章 1 订阅

在这里插入图片描述

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<String>()

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? = null

override 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<ArrayList<String>> {
    // 略...
    return Observable.empty<ArrayList<String>>()
}

到得到新的输入时,将前一次异步请求手动停止

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.

switchMapflatMap类似,也可用来进行Stream的切换,但当switchMap切换新Stream时,旧stream会自动停止。通过和flatMap的对比,体会一下其含义:

  • flatMap
    在这里插入图片描述

  • switchMap
    在这里插入图片描述

上述例子,经switchMap改造后,代码简洁了许多:

override fun onCreate(savedInstanceState: Bundle?) {
    // 略...

    queryPublisher.distinctUntilChanged()
            .debounce(500, TimeUnit.MILLISECONDS)
            .switchMap { search(it) }
            .subscribe({
                // 搜索结果显示
                Log.d(TAG, "result: ${it}")
            })
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值