ViewPager是Android支持库中最著名和使用最广泛的组件之一。 所有最简单的轮播,登机和滑条都制作在上面。 在2019年2月,AndroidX开发团队发布了ViewPager2。 让我们看看这些先决条件是什么,以及该组件的更新版本具有哪些优势。

ViewPager 2
在撰写本文时(2019年7月),提供了
ViewPager2的beta版本,这意味着可以解决以下提到的问题,并可以改进和扩展功能。 开发人员承诺将来会增加对TabLayout的支持(尽管它只能与第一个版本一起使用),优化适配器的性能,进行许多小的更正并最终确定文档。
整合性
该组件不随标准包装一起提供,而是单独连接。 为此,将以下行添加到模块的gradle脚本中的dependencies块中:
implementation "androidx.viewpager2:viewpager2:1.0.0-beta02"
实作
让我们从好消息开始:从第一版到第二版的过渡尽可能地简单,归结为导入的改变。 良好的旧语法没有被触及:
getCurrentItem()方法返回当前页面,
ViewPager2.onPageChangeCallback允许
您订阅寻呼机
状态 ,仍然通过
setAdapter()安装适配器
。
值得深入研究,因为很明显第一和第二寻呼机除接口外没有其他共同之处。 熟悉setAdapter()方法的实现不容置疑:
public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); }
是的,ViewPager2只是
RecyclerView的包装。 一方面,这是一个很大的好处,另一方面,这会增加头痛。 随着
PagerSnapHelper的出现,将
RecyclerView伪装成传单成为可能。 此类更改滚动的物理原理。 当用户松开手指时,
PagerSnapHelper会计算出最靠近列表中心线的列表项,并通过平滑的动画将其恰好对准中心。 因此,如果滑动足够尖锐,则列表将滚动到下一个元素,否则-动画将返回其原始状态。
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);

使用PagerSnapHelper时,请确保RecyclerView本身及其所有ViewHolders的宽度和高度都设置为MATCH_PARENT。 否则,SnapHelper的行为将无法预测,错误可能会在完全意外的地方发生。 尽管可能,所有这些使得制作高度小的元件的旋转木马相当耗时。
鉴于以上所有内容,在布局中,小部件将如下所示:
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" />
在与
ViewPager2相同的程序包中
,我们还可以找到
ScrollEventAdapter类,该类有助于维护语法的连续性。
ScrollEventAdapter实现
RecyclerView.OnScrollListener并将滚动事件
转换为OnPageChangeCallback事件。
@Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... }
现在,
OnPageChangeCallback不再由接口表示,而是由抽象类表示,该抽象类允许您仅覆盖必需的方法(在大多数情况下,您仅需要
nPageSelected(Int) ,当选择特定页面时才起作用):
main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) {
特色功能
值得注意的是setPageTransformer()方法,该方法将
ViewPager2.PageTransformer作为参数。 它为每个页面选择事件设置一个
回调 ,并为该页面设置自己的动画。
回调接收当前页面的
视图及其编号作为输入。 与该方法最接近的类似物是
RecyclerView中的
ItemAnimator 。
在该库的新版本中,添加了转换器的两个实现:
CompositePageTransformer和
MarginPageTransformer 。 第一个负责组合转换器,以便一次将多个转换应用于一个寻呼机,第二个负责在页面之间缩进:

此外,新的小部件还支持方向更改:只需调用
setOrientation()方法,您就可以通过上下滑动将寻呼机变成垂直列表:
main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL)
由于过渡到
RecyclerView,再次发生了这种情况:在引擎盖下,
称为LayoutManager的方向发生了变化,它负责显示列表项。 应当指出,将大量任务委托给其他类已经使新组件受益:新组件的清单变得更加紧凑和易读。
这不是乐趣的尽头。 在一个更新中,
ViewPager2获得了对
ItemDecoration的支持:一个用于装饰子级
View的
辅助类。 此机制可用于在元素,边框,单元格突出显示之间绘制分隔符。
装饰器已经有很多现成的实现,因为多年来,它们在与常规
RecyclerView一起工作时已被成功使用。 现在所有的发展都适用于寻呼机。 开箱即用的寻呼机分隔符的标准实现是可用的:
main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) )
随着2019年5月的下一次更新,
ViewPager2添加了另一个重要方法:
setOffscreenPageLimit(Int) 。 他负责在寻呼机中初始化中心左侧和右侧的多少个元素。 尽管
RecyclerView默认负责缓存和显示
View ,但是使用此方法,您可以显式设置要加载的所需项目数。
转接头
第一个寻呼机适配器的思想上的继承者是
FragmentStateAdapter :交互接口和类命名几乎相同。 所做的更改仅影响某些方法的命名。 如果较早时有必要实现抽象函数
getItem(position)以返回给定位置所需的
Fragment实例,并且可以用两种方式解释这种命名,现在此函数已重命名为
createFragment(position) 。 片段的总数由
getCount()函数像以前一样提供。
在接口的重要结构更改中,还应注意,适配器现在具有控制其元素生命周期的能力,因此,它与构造函数中的
FragmentManager一起,接受
Lifecycle对象 (
Activity或
Fragment) 。 因此,为了安全起见,将
saveState()和
restoreState()方法声明为final并关闭以进行继承。
FragmentViewHolder类负责将片段存储在
RecyclerView内部。
FragmentStateAdapter的
onCreateViewHolder()方法调用
FragmentViewHolder.create() 。
static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); }
调用onBindViewHolder()方法时,
会将当前位置的元素的标识符与
ViewHolder标识符
相关联 ,以进一步将片段附加到该片段:
final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position);
最后,将容器从
ViewHolder附加到
View层次结构时,将执行
FragmentTransaction ,将
Fragment添加到容器中:
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... }
因此,
出现了
ViewPager2的两种用法:通过直接从
RecyclerView.Adapter或
FragmentStateAdapter继承适配器类。
当然,您会有一个问题:为什么在存在正常运行的第一个版本的情况下,为什么要使用带有分片的第二个寻呼机和一个适配器? 使用大型动态数据列表时,
ViewPager远非“灵丹妙药”。 它非常适合创建带有一组静态图片或横幅的轮播,但是分页的新闻提要和大量的广告发布,过滤会产生坚硬而丑陋的怪物。 迟早,您肯定会遇到一种强烈的愿望,那就是要重写
RecyclerView上的所有内容。 现在,您不必这样做,因为传呼机本身就变成了它,它借用了其强大的功能来处理动态列表,同时将它们包装在常规语法中。
PagerAdapter唯一可以提供给
我们的是
notifyDataSetChanged()方法,该方法强制
ViewPager重新绘制所有呈现的列表项。 您可能会合理地注意到,没有人阻止我们存储现有元素的位置列表,并从
getItemPosition()方法返回
POSITION_UNCHANGED ,仅此而已。 但是,这种解决方案不能称其为美观,而且相当麻烦,而且,很难扩展列表中元素不断变化的情况,而不仅仅是顺序添加到最后。
FragmentStateAdapter具有完整的
RecyclerView.Adapter方法库,因此可以更灵活地配置重绘元素的逻辑。 此外,您可以与
FragmentStateAdapter一起使用
DiffUtil ,它使您几乎完全自动化了更改通知的工作。

注意! 为了使notify ...方法正常工作( notifyDataSetChanged除外),应该重新定义getItemId(Int)和ctaintainsItem(Long)方法。 这样做是因为默认实现仅查看页码,例如,如果在当前元素之后添加新元素,则不会添加该元素,因为getItemId将保持不变。 基于Int类型的元素列表覆盖这两种方法的示例:
override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) }
ViewPager2出现的
主要原因是不愿重新发明轮子。 一方面,
AndroidX开发
团队显然已经准备好放弃过时的
ViewPager ,并且肯定不会投资于扩展其功能。 是的,为什么? 毕竟,
RecyclerView已经知道所需的一切。 另一方面,取消和终止对如此广泛使用的组件的支持显然不会增加社区的忠诚度。
总结一下:
ViewPager2绝对值得关注,尽管目前并非没有严重的缺陷。
缺点
- 潮湿和大量错误(对于Beta版本可原谅);
- 亲密。 RecyclerView是ViewPager2的私有字段,它剥夺了我们很多机会:无法实现滑动 擦除或拖放 ( ItemTouchHelper直接连接到RecyclerView ),您不能以任何方式重新定义ItemAnimator ,不要直接访问LayoutManager并使用RecycledViewPool 。 但是,随着该组件新版本的发布,从RecyclerView继承的接口方法的数量正在增加(例如ItemDecoration ),我们希望将来可以添加缺少的方法。
优点
- 支持RecyclerView.Adapter的所有优点:将不同类型的元素合并到一个列表中,在滑动时直接添加和删除元素,更改时以动画方式重绘列表的内容;
- 支持各种通知方法和使用DiffUtil自动计算变化;
- 由于语法的连续性,易于过渡;
- 支持“开箱即用”的垂直和水平方向;
- RTL支持;
- 支持ItemDecorator ;
- 支持通过fakeScrollBy()滚动软件;
- 能够手动设置已加载项目的数量;
- 使用任何现成的开源解决方案来减少样板代码的能力 ,这是编写自定义RecyclerView.Adapter时不可避免的。 例如, EasyAdapter 。
总结一下,我想说
ViewPager2确实值得
仔细研究。 这是一个很有前途的,可扩展的,功能性的解决方案。 而且,尽管在生产中启动
新的小部件还为时过早,但是可以肯定地说,在完全发布之后,它可以并且应该完全取代其祖先。
对于那些大胆而果断的人(本文启发他们进行实验),
PagerSnapHelper出现在第28版
支持库中 ,这意味着您可以通过
自己创建
ViewPager2与
RecyclerView一起使用。
ViewPager2和
FragmentStateAdapter 的示例操作。
官方
发行说明 ViewPager2