ViewPager2
是 ViewPager
库的改进版本,可提供增强功能并解决使用 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
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
对少量固定数量的 fragment 进行分页时,请将FragmentStateAdapter
与ViewPager2
搭配使用。 - 当
ViewPager
使用FragmentStatePagerAdapter
分页浏览大量或未知的 fragment 时,请将FragmentStateAdapter
与ViewPager2
搭配使用。
构造函数参数
继承自 FragmentPagerAdapter
或 FragmentStatePagerAdapter
的基于 Fragment 的适配器类始终接受单个 FragmentManager
对象作为构造函数参数。为 ViewPager2
适配器类扩展 FragmentStateAdapter
时,您可以改为选择下列构造函数参数:
ViewPager2
对象所在的FragmentActivity
对象或Fragment
对象。在大多数情况下,这个选择更好。FragmentManager
对象和Lifecycle
对象。
直接从 RecyclerView.Adapter
继承的基于视图的适配器类不需要构造函数参数。
替换方法
您的适配器类还需要为 ViewPager2
替换与 ViewPager
不同的方法:
- 替换
getItemCount()
,而不是getCount()
。除了名称之外,此方法保持不变。 - 在基于 fragment 的适配器类中,替换
createFragment()
,而不是getItem()
。请确保新的createFragment()
方法在每次调用函数时都提供新的 fragment 实例,而不是重复使用实例。
总结
总而言之,如需转换 ViewPager
适配器类以便与 ViewPager2
配合使用,您必须做出以下更改:
- 将父类更改为
RecyclerView.Adapter
(对于视图分页)或FragmentStateAdapter
(对于 fragment 分页)。 - 更改基于 Fragment 的适配器类中的构造函数参数。
- 替换
getItemCount()
,而不是getCount()
。 - 在基于 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
集成的更改。如果您目前使用带有 TabLayout
对象的 ViewPager
来显示水平标签页以进行导航,则需要重构 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")
您还需要更改 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
对象的代码。虽然 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
,请参阅以下其他资源。
示例
- GitHub 上的 ViewPager2 示例
视频
- 新篇章:迁移到 ViewPager2(2019 年 Android 开发者峰会)