RecyclerPager

当 PagerSnapHelper 出现后,使用 RecyclerView 代替 ViewPager 越来越可行了。 RecyclerView 的动态更新是基础功能,而 ViewPager 的动态更新一直是这个控件的痛点。

使用 RecyclerView + PagerSnapHelper 简单展示 View 翻页是没有问题,不过想进一步实现 RecyclerView 动态显示 Fragment 就遇到各种组件的缺失了。

所以补一些 RecyclerView 相对 ViewPager 缺少的东西,写了个:RecyclerPager

项目包括一些工具类和控件

  • RecyclerPager

    • FragmentRecyclerAdapter:管理 Fragment 加载的 Adapter

    • SnapPageScrollListener:类似 ViewPager.OnPageChangeListener 的滚动监听器

    • PageRecyclerView:适合最外层使用,优化水平或竖直方向才触发滚动的 RecyclerView

  • SmartTabLayout2

    根据 https://github.com/ogaclejapan/SmartTabLayout 改造的 TabLayout,使用了 RecyclerPager 组件,很方便的就能支持 RecyclerView + PagerSnapHelper 处理 Tab 滚动和位移

RecyclerPager Demo


实现分析

一. ViewHolder 显示 Fragment

要在 RecyclerView.ViewHolder 内显示 Fragment。首先需要一个类似 android.support.v4.app.FragmentPagerAdapter 管理 Fragment 的动态绑定的 RecyclerView.Adapter。

使用 FragmentManager 和 FragmentTransaction 添加 Fragment 时,需要页面(FragmentContainer) 中一个 ViewGroup 的 Id 进行绑定。RecyclerView.ViewHolder 的几个生命周期对于我们怎样绑定 Fragment 很重要:

  1. onCreateViewHolder

    基于 FrameLayout 创建空的 ViewHolder

  2. onBindViewHolder

    基于 ViewCompat.generateViewId() 和当前 position 对 ViewHolder 中 itemView (FrameLayout)重新 setId, 保证 Id 唯一。

  3. onViewAttachedToWindow

    当 ViewHolder 开始在页面显示时,对 Fragment 进行 add 或 attach 以及 setUserVisibleHint 等处理。 注意,在 onBindViewHolder 时,不能马上操作 FragmentTransaction,因为 ViewHolder 的 itemView 还没有被添加到 ViewTree 中,FragmentContainer 是找不到设置的 Id 的

  4. onViewDetachedFromWindow

    ViewHolder 离屏不显示,只对 Fragment 进行 setUserVisibleHint 等操作,保证 Fragment 生命周期正确。

  5. onViewRecycled

    ViewHolder 开始被回收复用时,对绑定在 itemView 上的 Fragment 进行 detach 。

二. RecyclerView 滚动监听 SnapHelper 选中页面

我们还缺少 ViewPager.OnPageChangeListener 这样方便的回调。

  1. onPageScrollStateChanged(int state)
  2. onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
  3. onPageSelected(int position)

继承 RecyclerView.OnScrollListener,其中 onScrollStateChanged(RecyclerView recyclerView, int newState) 已经实现了第 1 个回调。 剩下两个回调需要在 onScrolled(RecyclerView recyclerView, int dx, int dy) 计算实现。

SnapHelper 的 findSnapViewcalculateDistanceToFinalSnap 能帮助我们确定选中 View 的位置和距离。
避免使用如 LinearLayoutManager.findFirstVisibleItemPosition 等实现类方法来计算。

只是在滚动中,例如 A、B页面滚动到 50% 之后时, findSnapView 命中的目标会 A 切换到 B。与 ViewPager 中 A 页面一定是滑动到 100% 才从 A 切换到 B 有些出入。

Author

Relex

Android Developer