美文网首页
Kotlin扩展方法配合RxJava过滤快速点击事件.md

Kotlin扩展方法配合RxJava过滤快速点击事件.md

作者: Leon_230 | 来源:发表于2020-06-29 23:55 被阅读0次

思路整理

因为项目中使用了RxAndroidRxBinding。所以我们处理快速点击事件时,都是通过throttleFirst来处理。具体使用方法如下:

定义一个ViewUtils类,核心代码如下

// 默认1000ms
private static final long THROTTLE_FIRST_EVENT_DURATION = 1000;

/**
* 过滤view重复点击,默认1000ms
*/
@CheckResult
@NonNull
public static Observable<?> clicks(@NonNull View view) {
        return RxView.clicks(view).compose( upstream -> upstream.throttleFirst(THROTTLE_FIRST_EVENT_DURATION, TimeUnit.MILLISECONDS));
}

在需要处理View快速点击事件的地方使用

ViewUtils.clicks(button)
    .subscribe {
        ToastUtils.showShort("debounce click")
    }.apply {
        mDispose.add(this)
    }

关于throttleFirst的大致原理我们稍微可以看一下 ObservableThrottleFirstTimedDebounceTimedObserver的代码

其实就是在设定的时间内过滤掉Observable发射过来的事件

@Override
public void onNext(T t) {
    if (!gate && !done) {
        gate = true;
        downstream.onNext(t);
        Disposable d = get();
            if (d != null) {
                d.dispose();
            }
        DisposableHelper.replace(this, worker.schedule(this, timeout, unit));
     }
}

这样的使用可以解决大部分View快速点击的问题。

然后,项目中RecyclerView的Adapter,我们使用了BaseRecyclerViewAdapterHelper库,所以我们处理RecyclerView的item事件是通过BaseQuickAdapter提供的setOnItemClickListener 处理,所以在这里,我们没办法使用ViewUtils的去除重复点击的方法了,但问题还是要解决的。

一开始,采用Java的思维方式去思考,考虑是否可以自定义一个Adapter继承自BaseQuickAdapter,覆写其内部实现点击事件的处理操作。后面转念一想,我们项目不是有使用kotlin么,为啥不直接用扩展方法来处理呢?然后翻看一下RxBinding的源码,发现大佬处理View的点击事件也是通过扩展方法处理,所以我们也可以依葫芦画瓢,copy一下他的代码改改处理成自己想要的结果。

RxView的源码如下

@CheckResult
fun View.clicks(): Observable<Unit> {
  return ViewClickObservable(this)
}

private class ViewClickObservable(
  private val view: View
) : Observable<Unit>() {

  override fun subscribeActual(observer: Observer<in Unit>) {
    if (!checkMainThread(observer)) {
      return
    }
    val listener = Listener(view, observer)
    observer.onSubscribe(listener)
    view.setOnClickListener(listener)
  }

  private class Listener(
    private val view: View,
    private val observer: Observer<in Unit>
  ) : MainThreadDisposable(), OnClickListener {

    override fun onClick(v: View) {
      if (!isDisposed) {
        observer.onNext(Unit)
      }
    }

    override fun onDispose() {
      view.setOnClickListener(null)
    }
  }
}

具体实现

修改完后代码如下所示:

/**
 * 扩展 BaseQuickAdapter 对 setOnItemClickListener 做过滤快速点击事件处理
 */
fun BaseQuickAdapter<*, *>.extItemClick(): Observable<AdapterItemInfo> {
    return ItemClickObservable(this)
            .compose {
                it.throttleFirst(THROTTLE_FIRST_EVENT_DURATION, TimeUnit.MILLISECONDS)
            }
}

private class ItemClickObservable(private val adapter: BaseQuickAdapter<*, *>) : Observable<AdapterItemInfo>() {
    override fun subscribeActual(observer: Observer<in AdapterItemInfo>) {
        if (!checkMainThread(observer)) {
            return
        }
        val listener = Listener(adapter, observer)
        observer.onSubscribe(listener)
        adapter.onItemClickListener = listener
    }

    private class Listener(
            private val view: BaseQuickAdapter<*, *>,
            private val observer: Observer<in AdapterItemInfo>
    ) : MainThreadDisposable(), BaseQuickAdapter.OnItemClickListener {
        override fun onDispose() {
            view.onItemClickListener = null
        }

        override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) {
            if (!isDisposed) {
                observer.onNext(AdapterItemInfo(adapter, view, position))
            }
        }
    }
}

data class AdapterItemInfo(val adapter: BaseQuickAdapter<*, *>,
                           val view: View,
                           val position: Int)

我们可以看到,其内部逻辑就是在ItemClickObservable里,创建一个Listener实现BaseQuickAdapter.OnItemClickListener,将adapter和Observer传递进去,然后在onItemClick方法里把点击事件转换成Observable事件流。然后我们拿到事件流后,就可以对这个事件流做过滤、变换等处理了。

所以extItemClick方法就是拿到item点击事件流来做一个过滤处理,在1秒内只响应第一个点击事件。这样就达到了我们过滤RecyclerView的item快速点击事件了。

注意事项

我们在看RxBinding的源码时,注释也给我们写出了注意事项,这里我们自己实现了Observable也是同理

/**
 * Create an observable which emits on `view` click events. The emitted value is
 * unspecified and should only be used as notification.
 *
 * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe
 * to free this reference.
 *
 * *Warning:* The created observable uses [View.setOnClickListener] to observe
 * clicks. Only one observable can be used for a view at a time.
 */
  1. 我们创建的Observable是持有了Adapter的强引用,所以我们在Unsubscribe时,要释放其引用。
  2. 在同一时刻,只有一个ItemClickObservable能被使用。

总结

  • 对于RxJava,我们可以多使用其操作符完成特定功能,而不仅仅只是把它当成一个处理异步操作的工具。
  • 多查看优秀第三方库源码,学习其编码思想。
  • kotlin的扩展方法很强大,可以很方便的扩展已有类的功能。

相关文章

网友评论

      本文标题:Kotlin扩展方法配合RxJava过滤快速点击事件.md

      本文链接:https://www.haomeiwen.com/subject/sdvzfktx.html