大家使用一些常见的 App 时都能发现 Tab 卡片切换效果,比如『AppGallery』『Mi Home』等:

这种交互将下方卡片和顶部 Tab 进行联动,用户可以通过左右滑动卡片,或者点击 Tab 切换卡片,特别适合将同一模组不同内容的页面合并展示。

得益于 Android 官方的良好封装,我们要实现这种方式也非常简单。

先来看布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

拆开两部分来看,顶部必然是使用 TabLayout 组件,下面则使用 ViewPager2 来实现。

既然有 ViewPager2,那之前肯定有 ViewPager,老版本的 ViewPager 是继承自 ViewGroup 自定义实现的,而 ViewPager2 虽然也继承自 ViewGroup,但却是基于 RecyclerView 封装的,解决了 ViewPager 的大部分痛点,大多数情况下我们优先选择 ViewPager2 来实现我们的需求。

TabLayoutViewPager2 都有自己的切换逻辑,那么我们需要做的,就是将它们关联起来,实现联动的效果。

class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.viewPager.adapter = object : FragmentStateAdapter(this) {
            override fun getItemCount() = 2
            override fun createFragment(position: Int) = when (position) {
                0 -> Fragment0()
                else -> Fragment1()
            }
        }
        TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
            when (position) {
                0 -> tab.text = "Tab 0"
                else -> tab.text = "Tab 1"
            }
        }.attach()
    }
}

ViewPager2 设置一个 adapter,并实现 FragmentStateAdapter 接口,重写 getItemCount()createFragment() 方法,分别返回 ViewPager2 的页面数量和对应位置的 Fragment。这部分属于 ViewPager2 的知识,这里不再赘述。

接下来,我们创建一个 TabLayoutMediator 对象,并调用它的 attach() 方法,将 TabLayoutViewPager2 关联起来。同时还可以根据需要,对 TabLayout.Tab 做一些自定义的处理操作。

这样,我们就实现了 TabLayoutViewPager2 联动。效果如下: