思路整理
因为项目中使用了RxAndroid,RxBinding。所以我们处理快速点击事件时,都是通过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
的大致原理我们稍微可以看一下 ObservableThrottleFirstTimed
中DebounceTimedObserver
的代码
其实就是在设定的时间内过滤掉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.
*/
- 我们创建的Observable是持有了Adapter的强引用,所以我们在Unsubscribe时,要释放其引用。
- 在同一时刻,只有一个ItemClickObservable能被使用。
总结
- 对于RxJava,我们可以多使用其操作符完成特定功能,而不仅仅只是把它当成一个处理异步操作的工具。
- 多查看优秀第三方库源码,学习其编码思想。
- kotlin的扩展方法很强大,可以很方便的扩展已有类的功能。
网友评论