美文网首页
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