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 的使用流程非常清晰。你只需要三步:

  1. 在布局里声明 ViewPager2
  2. 创建适配器(继承 FragmentStateAdapter)
  3. 把适配器设置给 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 提供了两种适配器:FragmentStateAdapterRecyclerView.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 的核心知识结构:

ViewPager2 知识体系 ViewPager2 基于 RecyclerView 支持垂直滑动 RTL 布局支持 FragmentStateAdapter createFragment() 自动生命周期管理 TabLayout 集成 TabLayoutMediator attach() / detach() 核心:RecyclerView 架构 + Fragment 生命周期 + Material 设计

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