22、ViewPager2:ViewPager2基础、FragmentStateAdapter、TabLayout集成
说实话,ViewPager2 这个组件,我盼了很久。老版的 ViewPager 在 Android 开发圈里用了好多年,大家也都习惯了。但用久了你会发现,它有不少让人头疼的地方——比如不支持垂直滑动,比如嵌套 RecyclerView 时滑动冲突严重。Google 后来推出了 ViewPager2,算是彻底重写了。我个人习惯叫它「VP2」,项目里也早就全面切换了。
这一章,我会带你从零搭建一个 VP2 的示例。咱们会聊到它的核心适配器 FragmentStateAdapter,以及如何跟 TabLayout 做集成。嗯,这些都是日常开发里最常用的场景。
22.1 ViewPager2 是什么?
ViewPager2 是 AndroidX 里提供的翻页容器。它继承自 ViewGroup,内部基于 RecyclerView 实现。说白了,它就是用 RecyclerView 的思维来做页面切换。
为什么说这是好事?因为 RecyclerView 的架构非常成熟——它自带 ViewHolder 复用、DiffUtil 差量更新、LayoutManager 灵活切换。VP2 把这些能力全继承过来了。
核心特点:
- 基于 RecyclerView,性能更好
- 支持垂直滑动(只需要设置 orientation)
- 支持 RTL(从右向左布局)
- 支持页面变换动画(PageTransformer)
- 与 TabLayout 集成更自然
我在项目中遇到过一个问题:老版 ViewPager 里嵌套了一个水平滑动的 RecyclerView,结果滑动事件经常被 VP 劫持,用户体验很差。换成 VP2 之后,通过 RecyclerView 的滑动嵌套机制,这个问题基本消失了。
22.2 基础用法:三步走
VP2 的使用流程非常清晰。你只需要三步:
- 在布局里声明 ViewPager2
- 创建适配器(继承 FragmentStateAdapter)
- 把适配器设置给 VP2
咱们先看布局文件:
<!-- activity_main.xml -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
就这么简单。不需要额外配置。接下来是适配器。
22.3 FragmentStateAdapter 详解
VP2 提供了两种适配器:FragmentStateAdapter 和 RecyclerView.Adapter。前者用于管理 Fragment 页面,后者用于管理普通 View。咱们重点讲 FragmentStateAdapter,因为这是最常用的。
FragmentStateAdapter 跟老版的 FragmentPagerAdapter 和 FragmentStatePagerAdapter 都不一样。它只有一个核心方法:createFragment(int position)。你不需要关心 Fragment 的添加和移除,VP2 内部全帮你处理好了。
class MyPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
private val pageCount = 4
override fun getItemCount(): Int = pageCount
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> PageFragment.newInstance("首页")
1 -> PageFragment.newInstance("发现")
2 -> PageFragment.newInstance("消息")
3 -> PageFragment.newInstance("我的")
else -> throw IllegalArgumentException("Invalid position: $position")
}
}
}
这里有个细节要注意:构造方法必须传入 FragmentActivity 或 Fragment。为什么?因为 VP2 需要知道 FragmentManager 和生命周期归属。我刚开始用的时候,传了一个 ApplicationContext 进去,结果直接崩溃了。嗯,这个坑我踩过。
小提示:如果你在 Fragment 里使用 VP2,构造适配器时传 this(即 Fragment 本身)即可。不要传 getActivity(),否则 Fragment 嵌套管理会出问题。
然后在 Activity 里设置:
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
val adapter = MyPagerAdapter(this)
viewPager.adapter = adapter
运行起来,你就能左右滑动了。是不是很简单?
22.4 FragmentStateAdapter 与 FragmentStatePagerAdapter 的区别
很多老开发者会问:VP2 的适配器跟老版有什么区别?我整理了一张表,你一看就明白:
| 对比项 | FragmentStatePagerAdapter(老) | FragmentStateAdapter(VP2) |
|---|---|---|
| 基类 | PagerAdapter | RecyclerView.Adapter |
| Fragment 管理 | 手动管理 FragmentTransaction | 自动管理,基于 Lifecycle |
| 状态保存 | 通过 saveState/restoreState | 通过 SavedStateRegistry |
| 滑动方向 | 仅水平 | 水平 + 垂直 |
| 差量更新 | 不支持 | 支持(通过 notifyDataSetChanged) |
你看,VP2 的适配器在架构上更现代。它跟 Lifecycle 深度绑定,Fragment 的创建和销毁完全由系统管理。你不需要再写 getItem() 和 instantiateItem() 这些老方法了。
22.5 与 TabLayout 集成
VP2 跟 TabLayout 的集成,是 Google 官方推荐的做法。TabLayout 来自 Material Design 库,它提供了一组标签,点击后可以切换到对应的页面。
集成方式有两种:
- 手动绑定:通过 TabLayoutMediator 连接
- 自动绑定:不推荐,因为 VP2 没有提供直接的 setTabLayout() 方法
咱们直接看标准做法:
// 布局里添加 TabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
然后在代码里绑定:
val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = when (position) {
0 -> "首页"
1 -> "发现"
2 -> "消息"
3 -> "我的"
else -> "未知"
}
}.attach()
这里有个关键点:TabLayoutMediator 必须在适配器设置之后调用。为什么?因为 Mediator 需要知道 VP2 的页面数量,而这个数量来自适配器的 getItemCount()。如果你先 attach 再 setAdapter,Mediator 拿到的 count 是 0,标签就显示不出来。
注意:TabLayoutMediator 的 attach() 方法只能调用一次。如果你需要更新标签,应该调用 detach() 再重新 attach()。不过我个人建议,如果标签是动态的,最好在创建时就把数据传进去,避免频繁 detach。
22.6 知识体系图
下面这张 SVG 图,帮你梳理了 VP2 的核心知识结构:
22.7 避坑指南
最后,我分享几个实际项目中踩过的坑:
- Fragment 嵌套 VP2 时,子 Fragment 不显示? 检查一下父 Fragment 是否调用了
getChildFragmentManager()。我曾经用getParentFragmentManager()传进去,结果子页面一片空白。 - 滑动卡顿? 看看是不是在 VP2 外面包了一层 NestedScrollView。VP2 本身是 RecyclerView,嵌套在 NestedScrollView 里会导致测量问题。解决方案是用
android:nestedScrollingEnabled="false"关闭 VP2 的嵌套滑动。 - TabLayout 标签不更新? 确认一下 attach() 是否在 setAdapter() 之后调用。我见过有人把顺序搞反,结果标签一直显示「Tab 0」「Tab 1」这样的默认值。
- 页面切换时 Fragment 重建? VP2 默认会缓存相邻页面。如果你希望所有页面都保持存活,可以设置
viewPager.offscreenPageLimit = pageCount - 1。但要注意,页面太多时内存会飙升。
嗯,VP2 的内容其实还有很多,比如 PageTransformer 动画、与 RecyclerView 的嵌套滑动协调等。但作为基础篇,咱们先把核心概念和集成方式掌握好。后面的章节会深入更多高级用法。
公众号:蓝海资料掘金营,微信deep3321