ViewPager2

2,897 阅读5分钟

ViewPager2 1.0.0 的主要功能

  • 对之前的 ViewPager 实现的改进: 

  • RTL(从右向左)布局支持 

  • 垂直方向支持 

  • 可靠的 Fragment 支持(包括处理底层 Fragment 集合的更改) 

  • 数据集更改动画(包括 DiffUtil 支持) 

  • 从之前的 ViewPager 实现中轻松迁移(API 尽可能一致)。请参阅 迁移指南

 请参阅 指南,了解如何使用 ViewPager2 在 Fragment 之间滑动。

⚠️注意:ViewPager2 是 final 类型的,不可通过继承它进行修改。

引入依赖

dependencies {        
    implementation "androidx.viewpager2:viewpager2:1.0.0"    
}

核心实现:RecyclerView

相关学习笔记可参考 juejin.cn/post/684490…

基本使用

一。普通 View 切换

因为 ViewPager2 的内部实现其实是 RecyclerView,它的 Adapter 就是 RecyclerView 的 Adapter,所以这一部分的使用就不赘述了。

二。Fragment 切换

ViewPager2 中新增了 FragmentStateAdapter,是一个抽象类,继承自 RecyclerView.Adapter ,替代了 ViewPager 的 FragmentStatePagerAdapter。

它使用的 ViewHolder 是 FragmentViewHolder,就是一个普通的仅有一个 FrameLayout 的 Holder,等待 Fragment 的视图在正确的时机被 add 进来。

它重写了 onCreateViewHolder、onBindViewHolder 两个方法。

placeFragmentInViewHolder 方法判断逻辑较多,就不粘贴代码了。看方法名,也能知道它是把 Fragment 添加到对应的 ViewHolder 中。 

在 onBindViewHolder 中,会获取对应位置的 Fragment,并添加到 ViewHolder 中。 

三。配合 TabLayout 使用

ViewPager2 应该怎么使用 Tablayout 呢?这需要我们认识一个新类 TabLayoutMediator,这个类是在 material-1.2.0 中新增的一个类,需要我们单独引入这个包,依赖如下:

implementation 'com.google.android.material:material:1.2.0-alpha03'

TabLayoutMediator 的构造方法接收三个参数,第一个参数为 TabLayout;第二个参数为 ViewPager2;第三个参数是 TabConfigurationStrategy,这是一个接口,该接口中有一个方法

onConfigureTab(@NonNull TabLayout.Tab tab, int position),第一个参数是当前 Tab,第二个当前 position,源码如下:  public interface TabConfigurationStrategy {
    /**
     * Called to configure the tab for the page at the specified position. Typically calls {@link
     * TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied.
     *
     * @param tab The Tab which should be configured to represent the title of the item at the given
     *     position in the data set.
     * @param position The position of the item within the adapter's data set.
     */
    void onConfigureTab(@NonNull TabLayout.Tab tab, int position);
}

接下来我们便可以通过 TabLayoutMediator 将 TabLayout 与 ViewPager2 关联起来了:

四。PageTransformer 页面动画设置

这儿没有进行尝试,可参考 juejin.cn/post/684490…

页面加载/缓存常见的设置

//设置应保留在当前可见页面两侧的页面数
viewPager.setOffscreenPageLimit(4);

//关闭预加载
((RecyclerView)viewPager.getChildAt(0)).getLayoutManager().setItemPrefetchEnabled(false);

//设置缓存数量,对应 RecyclerView 中的 mCachedViews,即屏幕外的视图数量
((RecyclerView)viewPager.getChildAt(0)).setItemViewCacheSize(4);

viewPager.setOffscreenPageLimit 设置应保留在当前可见页面两侧的页面数,即离屏加载数,对应视图已绑定,且已添加到父视图中。默认 -1

生效流程:

  1. 设置 mOffscreenPageLimit 的值; 

  2. mRecyclerView.requestLayout(); 触发布局,以便通过getExtraLayoutSize() 进行预取;

  3. mRecyclerView.requestLayout() 这个过程会触发 LinearLayoutManager 的 onLayoutChildren 方法;

  4. onLayoutChildren 中会触发 LinearLayoutManager.calculateExtraLayoutSpace 方法,计算 extraLayoutSpace 的值(pageSize * pageLimit); 

  5. extraLayoutSpace 这个值会影响 LayoutState 中 mExtraFillSpace 值。 对 mExtraFillSpace 的解释:如果要预布局尚不可见的项目,则使用此选项。 在回收时,mExtraFillSpace 的布局距离不会避免回收可见的孩子。 

  6. 在 fill 填充布局过程中会用到 mExtraFillSpace; 

layoutManager.setItemPrefetchEnabled 设置是否开启预加载,没有添加到父视图中。默认 true

方法介绍: 设置当UI线程在框架之间处于空闲状态时,是否应查询 LayoutManager 的视口之外的视图。 如果启用,LayoutManager 将在 API 21 或更高版本的设备上查询在视图系统遍历中的 items 去 inflate/绑定。默认值是 true。 在平台 API 级别 21 和更高版本上,UI 线程在将帧传递给 RenderThread 和在下一个 VSync 脉冲启动其下一帧之间处于空闲状态。 通过在此时间段内从窗口视图中预取,延迟 inflate 和视图绑定在滚动和甩动期间不太可能引起颠簸和停顿。 

启用预取后,会产生副作用,即扩展 View 缓存的有效大小(mCachedViews)以容纳预取的视图。 

 生效流程:

 1. onTouchEvent 中,MotionEvent.ACTION_MOVE 的时候,会根据 dx、dy 执行 GapWorker 的 run 方法; 在此过程中,会进行预加载操作:

  1. 从视图收集预取位置:

  2. 此时就用到了 layoutManager.setItemPrefetchEnabled 设置的值,如果可以预取,才会收集到要预取位置; 

  3. layout.mPrefetchMaxCountObserved = 需要预取数,默认为2; 

  4. mCachedViews 大小 = mRequestedCacheMax + layout.mPrefetchMaxCountObserved;

  5.  进行预取操作:

  6. 获取需要预取位置的 ViewHolder:尝试从缓存获取或直接创建给定位置的ViewHolder。如果超过了 {@link #FOREVER_NS} 的最后期限,则此方法提早返回,而不是在认为没有时间的情况下构造或绑定 ViewHolder 。 如果必须构造 ViewHolder 且剩余时间不足,则返回 null。如果需要 ViewHolder 且必须绑定,但没有足够的时间,则返回未绑定的持有者。在返回的对象上使用 {@link ViewHolder#isBound()} 进行检查。

  7.  根据 ViewHolder 状态将其添加到 mCahcedViews 或 mRecyclerPool 中; 

recyclerVIew.setItemViewCacheSize 设置要在屏幕外缓存的视图数量,没有添加到父视图中**。默认 2**

它设置的是:发送到常规回收视图池 mRecyclerPool 之前要在屏幕外缓存的视图数量,即 mCachedViews 的大小。 设置的具体参数为 mRequestedCacheMax。 和预加载操作无关,只是为了以后使用的时候不必重新绑定数据,提高响应速度。

****⚠️注意

viewPager.setOffscreenPageLimit(4) 和 layoutManager.setItemPrefetchEnabled(false) 是两个概念,并不冲突。不论有没有设置 layoutManager.setItemPrefetchEnabled,viewPager.setOffscreenPageLimit 设置的预加载数量都有效。因为它是通过计算 mExtraFillSpace 来控制需要预加载的视图。

手势滑动控制

相比 ViewPager 而言,ViewPager2 对手势控制更敏感一些。滑动路线是斜线的时候,Fragment 左右切换动画、当前 Fragment 的刷新动画,都会触发,造成不好的体验。所以,需要进行一下手动控制,当滑动路线不和 ViewPager2 的排列方向大致平行的时候,不触发Fragment 左右切换动画,只进行当前 Fragment 的刷新动画。

提供一种简单粗暴的做法:

另外,可以直接关闭用户的手势滑动  viewPager2.setUserInputEnabled(false);

参考链接

学不动也要学!深入了解ViewPager2

Androidx 中的 ViewPager 与 ViewPager2