Maxiee 的 RxJava 学习指南 (5) - RxBinding

2017-07-25

介绍

在上一篇中介绍了 RxBinding 的常见用法和实现原理. RxBinding 封装的控件远不止上一篇中介绍的那些, 因此这一篇中我们继续探索 RxBinding, 学习它对其他控件封装的使用.

本篇是介绍 RxBinding 的最后一篇, 相信在学习完这两篇之后, 我们不仅能够使用 RxBinding 封装的控件, 对于我们自定义的视图, 因为学习过了原理, 我们也能实现自己的 Binding. 因此, 在下一篇中我们将转向下一个专题的学习.

下面我们开始本篇的学习, 本篇所涉及的代码都位于 MaxieeRxLearning, 建议先将这个项目 Clone 下来参照着学习.

RxRecyclerView

RxBindings 提供了一个 rxbinding-recyclerview-v7, 顾名思义就是对 recyclerview 提供支持. 首先我们要导入依赖:

compile 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'

RxBindings 库的使用方法都是一样的: 我们先创建一个 View, 之后用 RxBindings 提供的方法网上一套, 就将 View 封装成为了一个 Observable, 就能订阅它的一些事件.

这个库提供了两个封装: RxRecyclerView 对应 RecyclerView, RxRecyclerViewAdapter 对应 Adapter, 我们分别来看.

RxRecyclerView

RxRecyclerView 一共提供了两个状态的观察:

  • scrollStateChanges 滚动状态
  • scrollEvents 滚动事件

scrollStateChanges 滚动状态

滚动状态定义在 RecyclerView 中:

// RecyclerView 当前没有滚动
public static final int SCROLL_STATE_IDLE = 0;

// RecyclerView 正在被拖动
public static final int SCROLL_STATE_DRAGGING = 1;

// 手已经离开屏幕, RecyclerView 正在做动画移动到最终位置
public static final int SCROLL_STATE_SETTLING = 2;

订阅方式:

RxRecyclerView
        .scrollStateChanges(mRecyclerView)
        .subscribe(scrollState -> {
            Log.d("maxiee", "scrollState = " + scrollState);
        });

在 LogCat 中看到:

07-18 15:34:51.580 D/maxiee: scrollState = 1
07-18 15:34:51.614 D/maxiee: scrollState = 2
07-18 15:34:52.281 D/maxiee: scrollState = 0
07-18 15:34:53.863 D/maxiee: scrollState = 1
07-18 15:34:53.896 D/maxiee: scrollState = 2
07-18 15:34:54.130 D/maxiee: scrollState = 0

scrollEvents 滚动事件

滚动事件是我们更常用的, 就要通过它的 dx 和 dy, 判断是像什么方向移动了:

RxRecyclerView.scrollEvents(mRecyclerView)
        .subscribe(scroll -> {
            Log.d("maxiee", "dx = " + scroll.dx() + ", dy = " + scroll.dy());
        });

LogCat:

07-18 15:37:59.208 D/maxiee: dx = 0, dy = 0
07-18 15:38:01.169 D/maxiee: dx = 0, dy = 13
07-18 15:38:01.180 D/maxiee: dx = 0, dy = 31
07-18 15:38:01.204 D/maxiee: dx = 0, dy = 29
07-18 15:38:01.213 D/maxiee: dx = 0, dy = 45
07-18 15:38:01.239 D/maxiee: dx = 0, dy = 34
07-18 15:38:01.247 D/maxiee: dx = 0, dy = 21

childAttachStateChangeEvents

观察 child view 的 detached 状态, 当 LayoutManager 或者 RecyclerView 认为不再需要一个 child view 时, 会调用这个方法. 如果 child view 占用资源, 应当进行资源释放. 具体代码为:

RxRecyclerView.childAttachStateChangeEvents(mRecyclerView).subscribe(event -> {
    Button button = ((SimpleAdapter.ViewHolder) event.child().getTag()).mButton;
    CharSequence text = button.getText();

    if (event instanceof RecyclerViewChildAttachEvent) {
        Log.d("maxiee", "attach " + text);
        return;
    }

    if (event instanceof RecyclerViewChildDetachEvent) {
        Log.d("maxiee", "detach " + text);
        return;
    }
});

其中可以看出, 我们对 event 的类型进行检查, 来判断是 attach 还是 detach.

RxRecyclerViewAdapter

RxRecyclerViewAdapter 中提供了对 Adapter 数据集变动的一个观察方法 dataChanges:

RxRecyclerViewAdapter.dataChanges(mSimpleAdapter).subscribe(simpleAdapter -> {
    Log.d("maxiee", "dataChanges");
});

上面代码的作用是如果数据发生变动, 就打一个 log.

我们创建一个按钮, 当点击的时候将 Adapter 的一个元素进行替换:

RxView.clicks(mButtonReplaceItem).subscribe(e -> {
    mSimpleAdapter.replaceItem(1, "Maxiee");
    mSimpleAdapter.notifyDataSetChanged();
});

这样, 当我们点击按钮后, 界面的第一个元素文字会更新, 同时也会记录 dataChanges log.

RxRecyclerView 小结

至此, rxbinding-recyclerview-v7 包提供的功能我们就全看完了, 一共就这么多.

对于这部分, 我有两个想法:

  1. rxbinding-recyclerview-v7 提供的功能太少了, 只绑定了两个回调
  2. 没有对于业务的封装, 我们平时使用往往对 recyclerview 进行二次封装, 方便使用, 对于这一点, 正好可以作为思考题进行练手, 比如思考怎么实现一个底部加载, 下拉刷新等等

Support v4

RxBindings 提供了一个 rxbinding-support-v4, 对 Support v4 进行封装, 主要提供了两种封装:

  • RxViewPager
  • RxMeuItemCompat

由于使用的方法以及实现的原理都大同小异, 这里只说重点了.

RxViewPager

pageScrollStateChanges

检测 ViewPager 的滑动状态, 这个跟前面的 RecyclerView 的 scrollStateChagnes 有些类似:

RxViewPager.pageScrollStateChanges(mViewPager).subscribe(integer -> {
    Log.d("maxiee", "pageScrollStateChanges " + integer);
});

Log:

07-25 10:56:42.070 D/maxiee: pageScrollStateChanges 1
07-25 10:56:42.100 D/maxiee: pageScrollStateChanges 2
07-25 10:56:42.340 D/maxiee: pageScrollStateChanges 0
07-25 10:56:46.650 D/maxiee: pageScrollStateChanges 1
07-25 10:56:46.800 D/maxiee: pageScrollStateChanges 2
07-25 10:56:47.440 D/maxiee: pageScrollStateChanges 0
07-25 10:56:47.740 D/maxiee: pageScrollStateChanges 1
07-25 10:56:48.350 D/maxiee: pageScrollStateChanges 2
07-25 10:56:48.990 D/maxiee: pageScrollStateChanges 0

其中数值的定义跟前面 RecyclerView 的 scrollStateChagnes 中的定义含义是一致的.

pageSelections

监听所选择的页面.

RxViewPager.pageSelections(mViewPager).subscribe(integer -> {
    Log.d("maxiee", "pageSelections " + integer);
});

Log:

07-25 11:03:26.100 D/maxiee: pageSelections 0
07-25 11:03:36.350 D/maxiee: pageSelections 1
07-25 11:03:37.620 D/maxiee: pageSelections 2

currentItem

这是一个 Consumer, 接收一个整型, 用来切换 ViewPager 的 Tab 页.

例如, 我使用 3 个按钮, 点击分别切换到某个页面:

RxView.clicks(mBtnTab1).map(o -> 0).subscribe(RxViewPager.currentItem(mViewPager));
RxView.clicks(mBtnTab2).map(o -> 1).subscribe(RxViewPager.currentItem(mViewPager));
RxView.clicks(mBtnTab3).map(o -> 2).subscribe(RxViewPager.currentItem(mViewPager));

Support v4 小节

我们以 ViewPager 为例探索了 rxbinding-support-v4 这个包.

从中可以看出, 在掌握了 RxBinding 的原理之后, 对于不同控件的封装的过程都是一样的.

有一点还需要强调, 前面对视图套上 RxBinding 的封装后, RxBinding 类的实例就对这个视图有了强引用, 这会导致内存泄漏问题.

解决的方法是使用第二篇中, 把每次订阅后得到的 Disposable 存放到 CompositeDisposable 中, 等到生命周期结束的时候统一进行清空解绑.

总结

至此, 我们通过两篇博客完成了对 RxBinding 的学习.

我们学习了对常见控件封装的使用, 一些常见的应用场景, 以及 RxBinding 的内部实现原理.

RxBinding 还有一些比较有意思的包:

  • rxbinding-appcompat-v7
  • rxbinding-design

里面提供的控件封装都非常实用.

这里就不在罗列它们的使用了, 用法都是一样的.

到目前为止, 我们对 RxJava 操作 UI 已经具备了一定的基础了, 从下一篇开始, 我们将探索新的主题.