简介
ViewPager2内部使用RecyclerView实现,并提供了增强功能
特性
- 支持水平、垂直方向布局
android:orientation = “vertical”
- 支持从右到左
android:layoutDirection = “rtl”
- 禁止滑动
setUserInputEnabled()
- 可修改Fragment集合
对可修改的Fragment集合进行分页浏览,底层集合更改时调用notifyDatasetChanged来更新页面
- 支持DiffUtil
支持局部更新,避免全局更新notifyDatasetChanged全量更新
- 支持模拟拖拽fakeDragBy
Adapter
由于是内部是RecyclerView,因此可以使用RecyclerView.Adapter<>
若子页为Fragment或Activity,则可使用FragmentStateAdapter
懒加载
当ViewPager2使用FragmentStateAdapter时,可通过setoffscreenPageLimit(int limit)设置离屏缓存数量,当limit<1时,不会进行预加载,默认为-1,VP则是默认为1
可实现加载布局,但不加载数据的懒加载,即在resume调用方法进行加载(handler执行)
当没有设置offscreenPageLimit离屏缓存时,VP2的RecyclerView会缓存前两个Item以及后面预抓取一个Item
设置offscreenPageLimit为1时,会在左右离屏新增一个缓存的Item,即缓存总共5个Item
一屏多页
设置VP2的left和right的padding,并设clipToPadding = false
嵌套滑动冲突
由于ViewPager2内部通过RecyclerView实现,其onInterceptTouchEvent方法返回isUserInputEnabled&&super.onInterceptTouchEvenet
嵌套滑动中的内部控件需要滑动时,设置父控件不拦截事件requestDisallowInterceptTouchEvent(true)
嵌套滑动中的内部控件不需要滑动时,设置父控件不拦截事件requestDisallowInterceptTouchEvent(false)
增量更新DiffUtil
使用DiffUtil进行增量更新,调高性能,使用notifyDatasetChanged进行全量更新
PageDiffUtil内部有方法areItemsTheSame和areContentsTheSame比较两个item和其内容是否相同,前者表示View是否可以复用,后者表示是否需要更新内容 前者返回true时才能调用后者,后者返回fasle才能调用getChangePayLoad,用于更新对应Item
//使用DiffUtil更新数据
val callback = PageDiffUtil(mItems, newItems)
val difResult = DiffUtil.calculateDiff(callback)
mItems.clear()
mItems.addAll(newItems)
difResult.dispatchUpdatesTo(adapter)
转场动画Transformer
val multiTransformer = CompositePageTransformer()
multiTransformer.addTransformer(ScaleInTransformer())
multiTransformer.addTransformer(MarginPageTransformer(10))
ViewPager2.setPageTransformer(multiTransformer)
RecyclerView缓存机制
RecyclerView通过内部类Recycler管理缓存,RecyclerView本身时一个ViewGroup,滑动时需要增删子View,Recycler缓存的是ViewHolder,便于复用子View和绑定的数据
四级缓存
- mAttachedScrap: ArrayList< ViewHolder >类型
缓存屏幕中可见范围的ViewHolder
不重新创建视图和绑定数据
- mCachedViews: ArrayList< ViewHolder >类型
缓存滑动时即将与RecyclerView分离的ViewHolder,按position或id缓存,默认缓存数量为2
不重新创建视图和绑定数据
- mViewCacheExtension: ViewCacheExtension类型
开发者自行实现的缓存
不重新创建视图和绑定数据
- mRecyclerPool: RecycledViewPool类型
ViewHolder缓存池,本质为SparseArray,key为ViewType(int),value存放的是ArrayList< ViewHolder>,ArrayList默认存放5个ViewHolder
不重新创建视图,但重新绑定数据
mAttachedScrap、mCachedViews官方视为同一级
RecyclerView滑动时触发onTouchEvent#onMove,此时会进行回收和复用ViewHolder,这部分工作由LayoutManager负责
当RecyclerView重新布局时会依次执行下面几个方法
onLayoutChildren():对RecyclerView进行布局的入口方法
fill():负责对剩余空间进行不断填充,调用layoutChunk()
layoutChunk():填充View,最终通过Recycler中找到合适的View
onLayoutChildren() —> fill() —> layoutChunk() —> next() —> getViewForPosition()
getViewForPosition() 是从RecyclerView的回收机制实现类Recycler中获取合适的View
getViewForPosition() 就是依次通过四级缓存查找对应的ViewHolder,若都找不到则重新创建
ViewCacheExtension
第三级缓存,开发者自定义缓存
mAttachedScrap用来处理可见屏幕的缓存,而mCachedViews里存储的数据根据position来缓存,里面的数据随时可能会被替换
mRecyclerPool则按ViewType为key存储,不能按position存储,且还得重新绑定数据,而业务汇总优势需要在特定位置一直展示某个view,其内容不变,为避免重新创建View和重新绑定数据
与ListView缓存机制对比
mAttachedScrap和mCachedViews、mRecyclerViewPool能够快速重用itemView
而RecyclerView的优势在于对mCachedViews的使用,无需重新创建和绑定数据
mRecyclerPool可供多个RecyclerView共同使用,对缓存机制进行补强和完善,对于频繁更新和局部刷新等,RecyclerView更强大和完善
offscreenPageLimit离屏缓存
setOffscreenPageLimit设置的是VP2的离屏显示个数,默认是-1,
因为RecyclerView中的布局是通过LayoutManager,所以真正进行离屏计算是在VP2.LinearLayoutManagerImpl#calculateExtraLayoutSpace()中
该方法计算的是LinearLayoutManager布局的额外空间,LinearLayoutManagerImpl继承自LinearLayoutManager。
getPageSize()表示ViewPager2的宽度,左右离屏大小都为getPageSize() * pageLimit。extraLayoutSpace[0]表示左边,extraLayoutSpace[1]表示右边。
比如设置offscreenPageLimit为1,可以认为是把屏幕扩大到3倍。
左右两边各有一个离屏PageSize的宽度(左右不可见)
FragmentStateAdapter缓存
FragmentStateAdapter继承自RecyclerView.Adapter,所以可以直接通过setAdapter设置给VP2。我们知道FragmentStateAdapter作为Adapter时,每个Item都是Fragment
每个Fragment内部根布局为FrameLayout,并设置唯一ID
FragmentStateAdapter内部有两个数组,分别存储position分别对Fragment和ID的映射表
当VP2滑动时,当前页前两个item会被缓存到mCachedViews中,超过2个时,将其转移到RecyclePool中
当 当前页为最后一页时,会缓存前三个页面
而当第一次加载时,没有onTouch操作,不会进行预抓取