ViewPager2
是 ViewPager
库的改进版本,提供增强的功能并解决了使用 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
对象,按照以下说明更新适配器类以扩展适当的抽象类
- 当
ViewPager
使用PagerAdapter
分页视图时,使用ViewPager2
时请使用RecyclerView.Adapter
。 - 当
ViewPager
使用FragmentPagerAdapter
分页少量固定数量的 fragment 时,使用ViewPager2
时请使用FragmentStateAdapter
。 - 当
ViewPager
使用FragmentStatePagerAdapter
分页大量或未知数量的 fragment 时,使用ViewPager2
时请使用FragmentStateAdapter
。
构造函数参数
继承自 FragmentPagerAdapter
或 FragmentStatePagerAdapter
的基于 fragment 的适配器类始终接受一个 FragmentManager
对象作为构造函数参数。为 ViewPager2
适配器类扩展 FragmentStateAdapter
时,你可以选择以下构造函数参数
ViewPager2
对象所在的FragmentActivity
对象或Fragment
对象。在大多数情况下,这是更好的选择。- 一个
FragmentManager
对象和一个Lifecycle
对象。
直接继承自 RecyclerView.Adapter
的基于视图的适配器类不需要构造函数参数。
替换方法
与 ViewPager
相比,你的适配器类还需要为 ViewPager2
替换不同的方法
- 替换
getCount()
,改用getItemCount()
。除了名称不同,此方法未改变。 - 在基于 fragment 的适配器类中,替换
getItem()
,改用createFragment()
。请确保你的新createFragment()
方法每次调用时都提供一个新的 fragment 实例,而不是重用现有实例。
总结
总之,要转换 ViewPager
适配器类以供 ViewPager2
使用,必须进行以下更改
- 对于分页视图,将超类更改为
RecyclerView.Adapter
;对于分页 fragment,将超类更改为FragmentStateAdapter
。 - 更改基于 fragment 的适配器类中的构造函数参数。
- 替换
getCount()
,改用getItemCount()
。 - 在基于 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 接口
ViewPager2
对 TabLayout
集成进行了更改。如果你当前使用 ViewPager
和 TabLayout
对象来显示用于导航的水平选项卡,则需要重构 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
,请参阅以下其他资源。
示例
- GitHub 上的ViewPager2 示例
视频
- 翻开新篇章:迁移到 ViewPager2 (Android Dev Summit '19)