前言
ViewPager2 出来有段时间了,也在项目中体验了一把(真香)。
首先我们先要知道的是,ViewPager2 是 AndroidX 的组件,所以我们需要升级到AndroidX 才能使用。
整体上来说ViewPager2是ViewPager的重构升级版,拥有更好的性能与功能。
主要功能
- 对之前的ViewPager实现的改进:
- RTL(从右向左)布局支持
- 垂直方向支持
- 可靠的
Fragment
支持(包括处理底层Fragment
集合的更改) - 数据集更改动画(包括
DiffUtil
支持)
- 从之前的
ViewPager
实现中轻松迁移(API 尽可能一致)。请参阅迁移指南和示例应用。
请参阅指南,了解如何使用 ViewPager2 在 Fragment 之间滑动。
RTL(从右向左)布局支持
之前做过阿语的国际化项目,适配ViewPager我们需要将数据进行反转,还要将ViewPager 当前默认页面设置到最后一页,这样做有个后遗症触发多个页面的创建性能并不是很好。
相较于ViewPager对RTL的不友好,ViewPager2内部已经支持RTL,让我们有跟多的精力在业务上。
垂直方向支持
因为ViewPager2内部分页是通过RecyclerView以及LinearLayoutManager来实现的,所以我们通过在XML中或者代码设置我们的方向。
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(SOURCE)
@IntDef({ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL})
public @interface Orientation {
}
private void setOrientation(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPager2);
if (Build.VERSION.SDK_INT >= 29) {
saveAttributeDataForStyleable(context, R.styleable.ViewPager2, attrs, a, 0, 0);
}
try {
setOrientation(
a.getInt(R.styleable.ViewPager2_android_orientation, ORIENTATION_HORIZONTAL));
} finally {
a.recycle();
}
}
/**
* Sets the orientation of the ViewPager2.
*
* @param orientation {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL}
*/
public void setOrientation(@Orientation int orientation) {
mLayoutManager.setOrientation(orientation);
mAccessibilityProvider.onSetOrientation();
}
复制代码
可靠的 Fragment
支持(包括处理底层 Fragment
集合的更改)
ViewPager2是通过ReycyclerView实现的,所以我们看下适配器相关的代码
StatefulAdapter
用于ViewPager2将所有已添加到FragmentManager或者已经数据持久化的Fragment数据 进行保存,方便后续数据的恢复,FragmentStateAdapter中有具体实现方法
public interface StatefulAdapter {
/** Saves adapter state */
@NonNull Parcelable saveState();
/** Restores adapter state */
void restoreState(@NonNull Parcelable savedState);
}
复制代码
FragmentViewHolder
在FragmentStateAdapter中使用,用于保存Fragment的container,而不是Fragment。因为我们的RecyclerView最强大的功能是View的复用,如果Fragment复用话那不就乱套了
public final class FragmentViewHolder extends ViewHolder {
private FragmentViewHolder(@NonNull FrameLayout container) {
super(container);
}
@NonNull static FragmentViewHolder create(@NonNull 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);
}
@NonNull FrameLayout getContainer() {
return (FrameLayout) itemView;
}
}
复制代码
FragmentStateAdapter
- FragmentStateAdapter继承自RecyclerView.Adapter,所以ViewPager2拥有刷新数据的能力,就像普通的Adapter使用;
- FragmentStateAdapter同样拥有控制所以Fragments数据持久化,生命周期的能力;
- mSavedStates保存需要效持久化的Fragment 数据(Bundle)。这些Fragment持久化数据都是之前已经添加到FragmentManager,现在暂时移出,减少内存消耗。等再次使用的时候再恢复数据。
- mFragments保存添加到FragmentManager的mFragments
- mFragmentMaxLifecycleEnforcer这个类的功能是负责Fragment的生命周期控制,通过调用
FragmentTransaction#setMaxLifecycle
控制已添加到FragmentManager的Fragment。如果是当前要显示的Fragment,则生命周期可以执行到RESUME 否则 STAERT。用于懒加载
ViewPager2 分析
成员
- CompositeOnPageChangeCallback用page事件的分发,里面保存了所有监听
ViewPager2.OnPageChangeCallback
观察者 - ScrollEventAdapter的作用是将
RecyclerView.OnScrollListener
事件转变为ViewPager2.OnPageChangeCallback
事件,给CompositeOnPageChangeCallback分发 - PageTransformerAdapter的作用是将页面的滑动事件转变为
PageTransformer
可以接受的事件,有关PageTransformer
的示例和视频,请参见缩小页面转换器和深度页面转换器部分。 - FakeDrag 的作用是用来实现模拟拖动的效果;
- PagerSnapHelper用于页面的手势滑动工具,实现页面切换
核心方法
private void initialize(Context context, AttributeSet attrs) {
mAccessibilityProvider = sFeatureEnhancedA11yEnabled
? new PageAwareAccessibilityProvider()
: new BasicAccessibilityProvider();
mRecyclerView = new RecyclerViewImpl(context);
mRecyclerView.setId(ViewCompat.generateViewId());
mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
mLayoutManager = new LinearLayoutManagerImpl(context);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
setOrientation(context, attrs);
mRecyclerView.setLayoutParams(
new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());
// Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
// attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
mScrollEventAdapter = new ScrollEventAdapter(this);
// Create FakeDrag before attaching PagerSnapHelper, same reason as above
mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
mPagerSnapHelper = new PagerSnapHelperImpl();
mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
// Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
// don't want to respond on the events sent out during the attach process
mRecyclerView.addOnScrollListener(mScrollEventAdapter);
mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
mScrollEventAdapter.setOnPageChangeCallback(mPageChangeEventDispatcher);
// Callback that updates mCurrentItem after swipes. Also triggered in other cases, but in
// all those cases mCurrentItem will only be overwritten with the same value.
final OnPageChangeCallback currentItemUpdater = new OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
if (mCurrentItem != position) {
mCurrentItem = position;
mAccessibilityProvider.onSetNewCurrentItem();
}
}
@Override
public void onPageScrollStateChanged(int newState) {
if (newState == SCROLL_STATE_IDLE) {
updateCurrentItem();
}
}
};
// Prevents focus from remaining on a no-longer visible page
final OnPageChangeCallback focusClearer = new OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
clearFocus();
if (hasFocus()) { // if clear focus did not succeed
mRecyclerView.requestFocus(View.FOCUS_FORWARD);
}
}
};
// Add currentItemUpdater before mExternalPageChangeCallbacks, because we need to update
// internal state first
mPageChangeEventDispatcher.addOnPageChangeCallback(currentItemUpdater);
mPageChangeEventDispatcher.addOnPageChangeCallback(focusClearer);
// Allow a11y to register its listeners after currentItemUpdater (so it has the
// right data). TODO: replace ordering comments with a test.
mAccessibilityProvider.onInitialize(mPageChangeEventDispatcher, mRecyclerView);
mPageChangeEventDispatcher.addOnPageChangeCallback(mExternalPageChangeCallbacks);
// Add mPageTransformerAdapter after mExternalPageChangeCallbacks, because page transform
// events must be fired after scroll events
mPageTransformerAdapter = new PageTransformerAdapter(mLayoutManager);
mPageChangeEventDispatcher.addOnPageChangeCallback(mPageTransformerAdapter);
attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}
复制代码
Viewpager2 最重要的初始化方法,通过上面的代码我们可以看出:通过添加RecyclerView当子View,让RecyclerView来管理每个页面数据,Viewpager2只充当容器
总结
其实ViewPager2
本身的源码是非常简单的,它的核心点就在各个组件当中,所以本文就不对ViewPager2
的内部源码进行分析。到此为止,我们对ViewPager2
的源码分析完毕。