ViewPager2
是 ViewPager
库的改进版本,它提供了增强的功能并解决了使用 ViewPager
的常见难题。如果您的应用已使用 ViewPager
,请阅读此页面以了解有关迁移到 ViewPager2
的更多信息。
如果您想在您的应用中使用 ViewPager2
并且目前未使用 ViewPager
,请阅读 使用 ViewPager2 在片段之间滑动 和 使用 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() 方法以编程方式设置此属性。
可修改的片段集合
ViewPager2
支持通过可修改的片段集合进行分页,调用 notifyDatasetChanged()
在底层集合更改时更新 UI。
这意味着您的应用可以在运行时动态修改片段集合,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
对象,请更新适配器类以扩展以下相应的抽象类:
- 当
ViewPager
使用PagerAdapter
来分页查看视图时,请将RecyclerView.Adapter
与ViewPager2
一起使用。 - 当
ViewPager
使用FragmentPagerAdapter
来分页查看少量固定数量的片段时,请将FragmentStateAdapter
与ViewPager2
一起使用。 - 当
ViewPager
使用FragmentStatePagerAdapter
来分页查看大量或未知数量的片段时,请将FragmentStateAdapter
与ViewPager2
一起使用。
构造函数参数
从 FragmentPagerAdapter
或 FragmentStatePagerAdapter
继承的基于片段的适配器类始终接受单个 FragmentManager
对象作为构造函数参数。当您为 ViewPager2
适配器类扩展 FragmentStateAdapter
时,您还可以选择以下构造函数参数:
ViewPager2
对象所在的FragmentActivity
对象或Fragment
对象。在大多数情况下,这是更好的选择。- 一个
FragmentManager
对象和一个Lifecycle
对象。
直接从 RecyclerView.Adapter
继承的基于视图的适配器类不需要构造函数参数。
覆盖方法
对于 ViewPager2
,您的适配器类还需要覆盖与 ViewPager
不同的方法。
- 覆盖
getItemCount()
,而不是getCount()
。除了名称之外,此方法没有更改。 - 在基于片段的适配器类中覆盖
createFragment()
,而不是getItem()
。确保您的新createFragment()
方法每次调用函数时都提供新的片段实例,而不是重用实例。
总结
总而言之,要转换一个用于 ViewPager2
的 ViewPager
适配器类,您必须进行以下更改:
- 将父类更改为
RecyclerView.Adapter
(用于分页浏览视图)或FragmentStateAdapter
(用于分页浏览片段)。 - 更改基于片段的适配器类中的构造函数参数。
- 重写
getItemCount()
,而不是getCount()
。 - 在基于片段的适配器类中重写
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
集成进行了更改。如果您目前使用带有TabLayout
对象的ViewPager
来显示用于导航的水平选项卡,则需要重构TabLayout
对象以与ViewPager2
集成。
TabLayout
已与ViewPager2
分离,现在作为Material Components的一部分提供。这意味着要使用它,您需要将相应的依赖项添加到您的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
的更多信息,请参阅以下其他资源。
示例
- ViewPager2示例(GitHub)
视频
- 翻页:迁移到ViewPager2(Android Dev Summit '19)