从 ViewPager 迁移到 ViewPager2

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

如果你想在你的应用中使用 ViewPager2 并且目前没有使用 ViewPager,请阅读 使用 ViewPager2 在片段之间滑动使用 ViewPager2 创建带有标签的滑动视图 以获取更多信息。

迁移到 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() 方法以编程方式设置此属性。

可修改的片段集合

ViewPager2 支持通过可修改的片段集合进行分页,调用 notifyDatasetChanged() 以在基础集合发生变化时更新 UI。

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

DiffUtil

ViewPager2 是基于 RecyclerView 构建的,这意味着它可以访问 DiffUtil 实用程序类。这带来了很多好处,但最值得注意的是,这意味着 ViewPager2 对象原生利用了 RecyclerView 类的数据集更改动画。

将你的应用迁移到 ViewPager2

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

更新 XML 布局文件

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

<!-- 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 的基于片段的适配器类始终接受一个 FragmentManager 对象作为构造函数参数。当你为 ViewPager2 适配器类扩展 FragmentStateAdapter 时,你将有以下构造函数参数选项

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

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

重写方法

你的适配器类还需要重写与 ViewPager 不同的方法,以便与 ViewPager2 配合使用

  • getItemCount() 重写 getCount()。除了名称外,此方法保持不变。
  • 在基于片段的适配器类中,用 createFragment() 重写 getItem()。确保你的新 createFragment() 方法每次调用该函数时始终提供一个新的片段实例,而不是重用实例。

总结

总之,要将 ViewPager 适配器类转换为与 ViewPager2 配合使用,你必须进行以下更改

  1. 将超类更改为 RecyclerView.Adapter(用于对视图进行分页),或 FragmentStateAdapter(用于对片段进行分页)。
  2. 更改基于片段的适配器类中的构造函数参数。
  3. getItemCount() 重写 getCount()
  4. 在基于片段的适配器类中,用 createFragment() 重写 getItem()

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 接口

ViewPager2 引入了对 TabLayout 集成的更改。如果你目前使用 ViewPagerTabLayout 对象一起显示用于导航的水平标签,则需要为与 ViewPager2 的集成重构 TabLayout 对象。

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 元素的位置。对于 ViewPagerTabLayout 元素被声明为 ViewPager 元素的子元素;但对于 ViewPager2TabLayout 元素直接声明在 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 的信息,请参阅以下其他资源。

示例

视频