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
集成的更改。如果您目前使用的 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
对象的代码。虽然 TabLayout
与 ViewPager
集成使用的是它自己的 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
,请参阅以下其他资源。
示例
- GoutHub 上的 ViewPager2 示例
视频
- 新篇章:迁移到 ViewPager2(2019 年 Android 开发者峰会)