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
生效流程:
-
设置 mOffscreenPageLimit 的值;
-
mRecyclerView.requestLayout(); 触发布局,以便通过getExtraLayoutSize() 进行预取;
-
mRecyclerView.requestLayout() 这个过程会触发 LinearLayoutManager 的 onLayoutChildren 方法;
-
onLayoutChildren 中会触发 LinearLayoutManager.calculateExtraLayoutSpace 方法,计算 extraLayoutSpace 的值(pageSize * pageLimit);
-
extraLayoutSpace 这个值会影响 LayoutState 中 mExtraFillSpace 值。 对 mExtraFillSpace 的解释:如果要预布局尚不可见的项目,则使用此选项。 在回收时,mExtraFillSpace 的布局距离不会避免回收可见的孩子。
-
在 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 方法; 在此过程中,会进行预加载操作:
-
从视图收集预取位置:
-
此时就用到了 layoutManager.setItemPrefetchEnabled 设置的值,如果可以预取,才会收集到要预取位置;
-
layout.mPrefetchMaxCountObserved = 需要预取数,默认为2;
-
mCachedViews 大小 = mRequestedCacheMax + layout.mPrefetchMaxCountObserved;
-
进行预取操作:
-
获取需要预取位置的 ViewHolder:尝试从缓存获取或直接创建给定位置的ViewHolder。如果超过了 {@link #FOREVER_NS} 的最后期限,则此方法提早返回,而不是在认为没有时间的情况下构造或绑定 ViewHolder 。 如果必须构造 ViewHolder 且剩余时间不足,则返回 null。如果需要 ViewHolder 且必须绑定,但没有足够的时间,则返回未绑定的持有者。在返回的对象上使用 {@link ViewHolder#isBound()} 进行检查。
-
根据 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);