从 ViewPager 迁移到 ViewPager2

ViewPager2ViewPager 库的改进版本,可提供增强型功能并解决使用 ViewPager 时遇到的一些常见问题。如果您的应用已使用 ViewPager,请阅读本页,详细了解如何迁移到 ViewPager2

如果您想在应用中使用 ViewPager2 但当前并未使用 ViewPager,请参阅使用 ViewPager2 在 Fragment 之间滑动使用 ViewPager2 创建包含标签页的滑动视图了解详情。

迁移到 ViewPager2 的优势

迁移的主要原因在于,ViewPager2 可以获得积极的开发支持,而 ViewPager 不行。不过,ViewPager2 也提供一些其他的具体优势。

垂直方向支持

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

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

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

从右到左支持

ViewPager2 支持从右到左 (RTL) 分页。系统会根据语言区域在适当的情况下自动启用 RTL 分页,不过您也可以通过设置 ViewPager2 元素的 android:layoutDirection 属性为其手动启用 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

ViewPager2RecyclerView 的基础上构建而成,这意味着它可以访问 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 继承的基于视图的适配器类不需要构造函数参数。

替换方法

您的适配器类还需要针对 ViewPager2 替换一些方法,这些方法与针对 ViewPager 替换的方法不同:

  • 替换 getItemCount(),而不是 getCount()。除名称外,此方法不变。
  • 在基于 Fragment 的适配器类中,替换 createFragment(),而不是 getItem()。请确保新的 createFragment() 方法在每次函数被调用时总会提供新的 Fragment 实例,而不会重复使用实例。

摘要

总而言之,如欲转换 ViewPager 适配器类以用于 ViewPager2,您必须进行以下更改:

  1. 将父类更改为 RecyclerView.Adapter 用于分页浏览视图,或更改为 FragmentStateAdapter 用于分页浏览 Fragment。
  2. 更改基于 Fragment 的适配器类中的构造函数参数。
  3. 替换 getItemCount(),而不是 getCount()
  4. 在基于 Fragment 的适配器类中,替换 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 集成的更改。如果您目前使用的 ViewPager 通过 TabLayout 对象显示水平标签页来进行导航,则需要重构 TabLayout 对象以便与 ViewPager2 集成。

TabLayout 已从 ViewPager2 中分离出来,现在作为材料组件的一部分提供。这意味着,如需使用该对象,您需要将相应的依赖项添加到您的 build.gradle 文件中:

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

您还需要更改 TabLayout 元素在 XML 布局文件层次结构中的位置。在 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 对象的代码。虽然 TabLayoutViewPager 集成使用的是它自己的 setupWithViewPager() 方法,但是如果要与 ViewPager2 集成,却需要使用 TabLayoutMediator 实例。

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,请参阅以下其他资源。

示例

视频