从 ViewPager 迁移到 ViewPager2

ViewPager2ViewPager 库的改进版本,提供增强的功能并解决了使用 ViewPager 时常见的困难。如果你的应用已经使用 ViewPager,请阅读本页面以了解如何迁移到 ViewPager2

如果你想在应用中使用 ViewPager2 且当前未在使用 ViewPager,请阅读使用 ViewPager2 在 fragment 之间滑动使用 ViewPager2 创建带 tab 的滑动视图以了解更多信息。

迁移到 ViewPager2 的好处

迁移的主要原因是 ViewPager2 正在积极开发支持,而 ViewPager 没有。此外,ViewPager2 还提供其他几个具体的优势。

支持垂直方向

ViewPager2 除了传统的水平分页外,还支持垂直分页。你可以通过设置其 android:orientation 属性来为 ViewPager2 元素启用垂直分页

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:orientation="vertical" />

你还可以使用 setOrientation() 方法以编程方式设置此属性。

支持从右到左

ViewPager2 支持从右到左 (RTL) 分页。RTL 分页会在适当的情况下根据语言区域自动启用,但你也可以通过设置其 android:layoutDirection 属性来手动为 ViewPager2 元素启用 RTL 分页

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layoutDirection="rtl" />

你还可以使用 setLayoutDirection() 方法以编程方式设置此属性。

可修改的 fragment 集合

ViewPager2 支持通过可修改的 fragment 集合进行分页,并在基础集合发生变化时调用 notifyDatasetChanged() 来更新界面。

这意味着你的应用可以在运行时动态修改 fragment 集合,ViewPager2 将正确显示修改后的集合。

DiffUtil

ViewPager2 构建于 RecyclerView 之上,这意味着它可以访问 DiffUtil 实用程序类。这会带来多项好处,其中最显著的是 ViewPager2 对象可以原生利用 RecyclerView 类的数据集更改动画。

将应用迁移到 ViewPager2

按照以下步骤将应用中的 ViewPager 对象更新为 ViewPager2

更新 XML 布局文件

首先,将 XML 布局文件中的 ViewPager 元素替换为 ViewPager2 元素

<!-- A ViewPager element -->
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- A ViewPager2 element -->
<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

更新适配器类

使用 ViewPager 时,必须扩展向对象提供新页面的适配器类。根据用例,ViewPager 使用了三个不同的抽象类。ViewPager2 只使用两个抽象类。

对于要转换为 ViewPager2 对象的每个 ViewPager 对象,按照以下说明更新适配器类以扩展适当的抽象类

构造函数参数

继承自 FragmentPagerAdapterFragmentStatePagerAdapter 的基于 fragment 的适配器类始终接受一个 FragmentManager 对象作为构造函数参数。为 ViewPager2 适配器类扩展 FragmentStateAdapter 时,你可以选择以下构造函数参数

  • ViewPager2 对象所在的 FragmentActivity 对象或 Fragment 对象。在大多数情况下,这是更好的选择。
  • 一个 FragmentManager 对象和一个 Lifecycle 对象。

直接继承自 RecyclerView.Adapter 的基于视图的适配器类不需要构造函数参数。

替换方法

ViewPager 相比,你的适配器类还需要为 ViewPager2 替换不同的方法

  • 替换 getCount(),改用 getItemCount()。除了名称不同,此方法未改变。
  • 在基于 fragment 的适配器类中,替换 getItem(),改用 createFragment()。请确保你的新 createFragment() 方法每次调用时都提供一个新的 fragment 实例,而不是重用现有实例。

总结

总之,要转换 ViewPager 适配器类以供 ViewPager2 使用,必须进行以下更改

  1. 对于分页视图,将超类更改为 RecyclerView.Adapter;对于分页 fragment,将超类更改为 FragmentStateAdapter
  2. 更改基于 fragment 的适配器类中的构造函数参数。
  3. 替换 getCount(),改用 getItemCount()
  4. 在基于 fragment 的适配器类中,替换 getItem(),改用 createFragment()

Kotlin

// A simple ViewPager adapter class for paging through fragments
class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
    override fun getCount(): Int = NUM_PAGES

    override fun getItem(position: Int): Fragment = ScreenSlidePageFragment()
}

// An equivalent ViewPager2 adapter class
class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}

Java

// A simple ViewPager adapter class for paging through fragments
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public ScreenSlidePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

// An equivalent ViewPager2 adapter class
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
    public ScreenSlidePagerAdapter(FragmentActivity fa) {
        super(fa);
    }

    @Override
    public Fragment createFragment(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getItemCount() {
        return NUM_PAGES;
    }
}

重构 TabLayout 接口

ViewPager2TabLayout 集成进行了更改。如果你当前使用 ViewPagerTabLayout 对象来显示用于导航的水平选项卡,则需要重构 TabLayout 对象以与 ViewPager2 集成。

TabLayout 已与 ViewPager2 解耦,现在是Material 组件的一部分。这意味着为了使用它,你需要将相应的依赖项添加到 build.gradle 文件中

Groovy

implementation "com.google.android.material:material:1.1.0-beta01"

Kotlin

implementation("com.google.android.material:material:1.1.0-beta01")

你还需要更改 XML 布局文件层次结构中 TabLayout 元素的位置。使用 ViewPager 时,TabLayout 元素声明为 ViewPager 元素的子元素;但使用 ViewPager2 时,TabLayout 元素声明在 ViewPager2 元素的正上方,位于同一级别

<!-- A ViewPager element with a TabLayout -->
<androidx.viewpager.widget.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

</androidx.viewpager.widget.ViewPager>

<!-- A ViewPager2 element with a TabLayout -->
<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/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

最后,你必须更新将 TabLayout 对象附加到 ViewPager 对象的代码。虽然 TabLayout 使用其自己的 setupWithViewPager() 方法与 ViewPager 集成,但它需要一个 TabLayoutMediator 实例才能与 ViewPager2 集成。

此外,TabLayoutMediator 对象还负责为 TabLayout 对象生成页面标题,这意味着适配器类不需要替换 getPageTitle()

Kotlin

// Integrating TabLayout with ViewPager
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        tabLayout.setupWithViewPager(viewPager)
    }
    ...
}

class DemoCollectionPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {

    override fun getCount(): Int  = 4

    override fun getPageTitle(position: Int): CharSequence {
        return "OBJECT ${(position + 1)}"
    }
    ...
}

// Integrating TabLayout with ViewPager2
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
    }
    ...
}

Java

// Integrating TabLayout with ViewPager
public class CollectionDemoFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);
    }
    ...
}

public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
    ...
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "OBJECT " + (position + 1);
    }
    ...
}

// Integrating TabLayout with ViewPager2
public class CollectionDemoFragment : Fragment() {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        new TabLayoutMediator(tabLayout, viewPager,
                (tab, position) -> tab.setText("OBJECT " + (position + 1))
        ).attach();
    }
    ...
}

支持嵌套可滚动元素

在滚动视图方向与包含它的 ViewPager2 对象方向相同的情况下,ViewPager2 原生不支持嵌套滚动视图。例如,在垂直方向的 ViewPager2 对象内部的垂直滚动视图将无法正常滚动。

要在方向相同的 ViewPager2 对象内部支持滚动视图,当你希望滚动嵌套元素时,必须在 ViewPager2 对象上调用 requestDisallowInterceptTouchEvent()ViewPager2 嵌套滚动示例展示了如何使用通用的自定义包装布局来解决此问题。

其他资源

要详细了解 ViewPager2,请参阅以下其他资源。

示例

视频