> 文章列表 > ViewPager2的源码分析

ViewPager2的源码分析

ViewPager2的源码分析

一 ViewPager2新特性

基于RecyclerView实现。这意味着RecyclerView的优点将会被ViewPager2所继承。

2.支持竖直滑动。只需要一个参数就可以改变滑动方向。

3.支持关闭用户输入。通过setUserInputEnabled来设置是否禁止用户滑动页面

4.支持通过编程方式滚动。通过fakeDragBy(offsetPx)代码模拟用户滑动页面。

5.CompositePageTransformer 支持同时添加多个PageTransformer。

6.支持DiffUtil ,可以添加数据集合改变的item动画。

7.支持RTL (right-to-left)布局。

二 基本使用

  1. 引入viewpager2库
// module的build.gradle文件
implementation("androidx.viewpager2:viewpager2:1.0.0")

  2 在布局中添加viewpager2

  

<androidx.viewpager2.widget.ViewPager2android:id="@+id/viewPager"android:layout_width="match_parent"android:layout_height="match_parent" />

3 继承FragmentStateAdapter创建adapter

class DemoAapdater(activity: FragmentActivity) : FragmentStateAdapter(activity) {override fun getItemCount(): Int {return 6}override fun createFragment(position: Int): Fragment {val demoFragment = DemoFragment()demoFragment.arguments = Bundle().apply {putInt("TEXT", position)}return demoFragment}
}

4  为viewpager2设置adapter

demoAapdater = DemoAapdater(this)
viewPager2.adapter = demoAapdater

三 ViewPager2源码分析

  ViewPager初始化

    private void initialize(Context context, AttributeSet attrs) {// .... 省约部分// 初始化RecyclerViewmRecyclerView = new RecyclerViewImpl(context);mRecyclerView.setId(ViewCompat.generateViewId());// 初始化LayoutManagermLayoutManager = new LinearLayoutManagerImpl(context);mRecyclerView.setLayoutManager(mLayoutManager);setOrientation(context, attrs);mRecyclerView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());// 创建滑动事件转换器的对象mScrollEventAdapter = new ScrollEventAdapter(mLayoutManager);// 创建模拟拖动事件的对象mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);// 创建PagerSnapHelper对象,用来实现页面切换的基本效果mPagerSnapHelper = new PagerSnapHelperImpl();mPagerSnapHelper.attachToRecyclerView(mRecyclerView);mRecyclerView.addOnScrollListener(mScrollEventAdapter);// ······}

1.其中mRecyclerView = new RecyclerViewImpl(context);和mLayoutManager = new LinearLayoutManagerImpl(context); 属于重新封装的RecyclerView和LinearLayoutManager, 是分别为了处理事件拦截和ViewPager缓存页面的问题。

2.给RecyclerView设置了滑动监听事件,涉及到的组件是ScrollEventAdapter,后面的基本功能都需要这个组件的支持; (2) 设置了PagerSnapHelper,目的是实现切面切换的效果

 1.PagerSnapHelper分析

这个类主要继承了SnapHelper 然后实现了里面3个重要的方法

calculateDistanceToFinalSnap()
findSnapView()
findTargetSnapPosition()

这三个方法在RecyclerView 滑动时调用

1.手指在快速滑动一个RecyclerView,在手指离开屏幕之前,如上的三个方法都不会被调用。

2.如果手指如果手指离开了屏幕,接下来就是Fling事件来滑动RecyclerView,在Fling事件触发之际,findTargetSnapPosition方法会被调用,此方法的作用就是用来计算Fling事件能滑动到位置。

@Overridepublic boolean onFling(int velocityX, int velocityY) {RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();if (layoutManager == null) {return false;}RecyclerView.Adapter adapter = mRecyclerView.getAdapter();if (adapter == null) {return false;}int minFlingVelocity = mRecyclerView.getMinFlingVelocity();return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)1&& snapFromFling(layoutManager, velocityX, velocityY);}
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,int velocityY) {if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {return false;}RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);if (smoothScroller == null) {return false;}int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);if (targetPosition == RecyclerView.NO_POSITION) {return false;}smoothScroller.setTargetPosition(targetPosition);layoutManager.startSmoothScroll(smoothScroller);return true;}

3.当Fling事件结束之际,RecyclerView会回调SnapHelper内部OnScrollListener接口的onScrollStateChanged方法。

 private final RecyclerView.OnScrollListener mScrollListener =new RecyclerView.OnScrollListener() {boolean mScrolled = false;@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {mScrolled = false;1 此时RecyclerView的滑动状态为   RecyclerView.SCROLL_STATE_IDLE,所以就会分别调用findSnapView方法来找到需要显示在RecyclerView的最前面的View。找到目标View之后,就会调用calculateDistanceToFinalSnap方法来计算需要滑动的距离,然后调动RecyclerView相关方法进行滑动。snapToTargetExistingView();}}@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {if (dx != 0 || dy != 0) {mScrolled = true;}}};

接着分析 

snapToTargetExistingView

当RecyclerView在Fling时,如果想要不去拦截Fling时间,想让RecyclerView开心的Fling,可以直接在findTargetSnapPosition方法返回RecyclerView.NO_POSITION即可,或者我们可以在findTargetSnapPosition方法来计算滑动的最终位置,然后通过SmoothScroller来实现滑动。。

 /* Snaps to a target view which currently exists in the attached {@link RecyclerView}. This* method is used to snap the view when the {@link RecyclerView} is first attached; when* snapping was triggered by a scroll and when the fling is at its final stages.*/void snapToTargetExistingView() {if (mRecyclerView == null) {return;}RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();if (layoutManager == null) {return;}注释1 View snapView = findSnapView(layoutManager);if (snapView == null) {return;}注释2 int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);if (snapDistance[0] != 0 || snapDistance[1] != 0) {mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);}}

因为viewPager2是不支持快速滑动Filing事件的,所以在PagerSnapHelper中的findTargetSnapPosition()方法中做了处理下面看看

@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,int velocityY) {// ······// 1 找到与当前View相邻的View,包括左相邻和右响铃,并且计算滑动的距离for (int i = 0; i < childCount; i++) {final View child = layoutManager.getChildAt(i);if (child == null) {continue;}final int distance = distanceToCenter(layoutManager, child, orientationHelper);if (distance <= 0 && distance > distanceBefore) {// Child is before the center and closer then the previous bestdistanceBefore = distance;closestChildBeforeCenter = child;}if (distance >= 0 && distance < distanceAfter) {// Child is after the center and closer then the previous bestdistanceAfter = distance;closestChildAfterCenter = child;}}// 2 根据滑动的方向来返回的相应位置final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);if (forwardDirection && closestChildAfterCenter != null) {return layoutManager.getPosition(closestChildAfterCenter);} else if (!forwardDirection && closestChildBeforeCenter != null) {return layoutManager.getPosition(closestChildBeforeCenter);}// 3 最后兜底计算View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter;if (visibleView == null) {return RecyclerView.NO_POSITION;}int visiblePosition = layoutManager.getPosition(visibleView);int snapToPosition = visiblePosition+ (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1);if (snapToPosition < 0 || snapToPosition >= itemCount) {return RecyclerView.NO_POSITION;}return snapToPosition;
}

从上面看出为了拦截Filing事件,PagerSnapHelper中的findTargetSnapPosition()方法直接返回当前ItemView的上一个ItemView或者下一个ItemView的位置,只要不返回RecyclerView.NO_POSITION就不会有Fling效果! ——> 怎么阻止的Fling事件的触发呢?看看原因:(1)会调用父类(SnapHelper)里面的snapFromFling方法,只要findTargetSnapPosition方法返回不为RecyclerView.NO_POSITION,就会直接走startSmotthiScroll(),所以RecyclerView最终滑到的位置为当前位置的上一个或者下一个,不会产生Fling的效果。

接着分析findSnapView 

    public View findSnapView(RecyclerView.LayoutManager layoutManager) {if (layoutManager.canScrollVertically()) {return findCenterView(layoutManager, getVerticalHelper(layoutManager));} else if (layoutManager.canScrollHorizontally()) {return findCenterView(layoutManager, getHorizontalHelper(layoutManager));}return null;}
    private View findCenterView(RecyclerView.LayoutManager layoutManager,OrientationHelper helper) {int childCount = layoutManager.getChildCount();if (childCount == 0) {return null;}View closestChild = null;final int center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;int absClosest = Integer.MAX_VALUE;for (int i = 0; i < childCount; i++) {final View child = layoutManager.getChildAt(i);int childCenter = helper.getDecoratedStart(child)+ (helper.getDecoratedMeasurement(child) / 2);int absDistance = Math.abs(childCenter - center);//注释说了很清楚,滑动中间时相邻的那个最近,就确定距离屏幕中心最近的页面/* if child center is closer than previous closest, set it as closest  */if (absDistance < absClosest) {absClosest = absDistance;closestChild = child;}}return closestChild;}

1 当recyclerView滑动完之后,就会调用findSnapView这个方法,来确认最终位置的ItemView

2 在findSnapView内部,调用findCenterView方法,找到当前中心距离屏幕中心最近的ItemView

距离屏幕中心最近的页面。findCenterView方法的作用便是如此。

接着分析 snapToTargetExistingView 中注释2处 calculateDistanceToFinalSnap 方法

public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,@NonNull View targetView) {int[] out = new int[2];if (layoutManager.canScrollHorizontally()) {out[0] = distanceToCenter(layoutManager, targetView,getHorizontalHelper(layoutManager));} else {out[0] = 0;}if (layoutManager.canScrollVertically()) {out[1] = distanceToCenter(layoutManager, targetView,getVerticalHelper(layoutManager));} else {out[1] = 0;}return out;
}
    private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,@NonNull View targetView, OrientationHelper helper) {final int childCenter = helper.getDecoratedStart(targetView)+ (helper.getDecoratedMeasurement(targetView) / 2);final int containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;return childCenter - containerCenter;}

由上面的代码可知

calculateDistanceToFinalSnap()方法就是计算RecyclerView需要滑动的距离,主要通过distanceToCenter方法来计算,确定最终要展示的positon

PagerSnapHelper在最后总结 PagerSnapHelper可以实现页面切换的效果

(1)首先阻止RecyclerView的Fling事件,阻止的方式就是重写findTargetSnapPosition方法,当RecyclerView触发了Fling事件之后,直接滑动到下一个或者上一个。

(2)如果RecyclerView没有触发Fling事件,或者Fling阶段未能滑动到正确位置,此时需要findSnapView方法和calculateDistanceToFinalSnap来保证滑动到正确的页面。

2.ScrollEventAdapter分析

  //表示当前ViewPager2处于停止状态private static final int STATE_IDLE = 0;//表示当前ViewPager2处于手指拖动状态private static final int STATE_IN_PROGRESS_MANUAL_DRAG = 1;//表示当前ViewPager2处于缓慢滑动的状态。这个状态只在调用了ViewPager2的setCurrentItem方法才有可能出现。private static final int STATE_IN_PROGRESS_SMOOTH_SCROLL = 2;//表示当前ViewPager2处于迅速滑动的状态。这个状态只在调用了ViewPager2的setCurrentItem方法才有可能出现。private static final int STATE_IN_PROGRESS_IMMEDIATE_SCROLL = 3;//表示当前ViewPager2未使用手指滑动,而是通过FakerDrag实现的。private static final int STATE_IN_PROGRESS_FAKE_DRAG = 4;

ScrollEventAdapter实现了recyclerView的OnScrollListener事件,我们只需要看onScrollStateChanged()和onScolled方法

1.onScrollStateChanged()

public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {// 1. 开始拖动 会调用startDrag方法表示拖动开始if ((mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG|| mScrollState != SCROLL_STATE_DRAGGING)&& newState == RecyclerView.SCROLL_STATE_DRAGGING) {startDrag(false);return;}// 2. 拖动手势的释放,此时ViewPager2会准备滑动到正确的位置if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_SETTLING) {// Only go through the settling phase if the drag actually moved the pageif (mScrollHappened) {dispatchStateChanged(SCROLL_STATE_SETTLING);mDispatchSelected = true;}return;}//3.滑动结束 此时ScrollEventAdapter会调用相关的方法更新状态// Drag is finished (dragging || settling -> idle)if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_IDLE) {boolean dispatchIdle = false;updateScrollEventValues();if (!mScrollHappened) {if (mScrollValues.mPosition != RecyclerView.NO_POSITION) {dispatchScrolled(mScrollValues.mPosition, 0f, 0);}dispatchIdle = true;} else if (mScrollValues.mOffsetPx == 0) {dispatchIdle = true;if (mDragStartPosition != mScrollValues.mPosition) {dispatchSelected(mScrollValues.mPosition);}}if (dispatchIdle) {dispatchStateChanged(SCROLL_STATE_IDLE);resetState();}}if (mAdapterState == STATE_IN_PROGRESS_SMOOTH_SCROLL&& newState == RecyclerView.SCROLL_STATE_IDLE && mDataSetChangeHappened) {updateScrollEventValues();if (mScrollValues.mOffsetPx == 0) {if (mTarget != mScrollValues.mPosition) {dispatchSelected(mScrollValues.mPosition == NO_POSITION ? 0 : mScrollValues.mPosition);}dispatchStateChanged(SCROLL_STATE_IDLE);resetState();}}}

2.onScrolled()

public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {mScrollHappened = true;// 1 updateScrollEventValues();if (mDispatchSelected) {// 2 mDispatchSelected = false;boolean scrollingForward = dy > 0 || (dy == 0 && dx < 0 == isLayoutRTL());mTarget = scrollingForward && mScrollValues.mOffsetPx != 0? mScrollValues.mPosition + 1 : mScrollValues.mPosition;if (mDragStartPosition != mTarget) {dispatchSelected(mTarget);}} else if (mAdapterState == STATE_IDLE) {// 调用了setAdapter方法dispatchSelected(mScrollValues.mPosition);}dispatchScrolled(mScrollValues.mPosition, mScrollValues.mOffset, mScrollValues.mOffsetPx);// 3if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)&& mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {dispatchStateChanged(SCROLL_STATE_IDLE);resetState();}
}

1 处新且计算当前position和offset的值

2 拖动手势释放,ViewPager2正在滑动到正确的位置

3 因为调用了setCurrentItem(x, false)不会触发IDLE状态的产生

onScrolled()方法中主要是调用updateScrollEventValues()方法更新状态ScrollEventValues里面3个变量,(2)调用相关dispatch...方法更新状态和分发状态下去

    private static final class ScrollEventValues {int mPosition;//从开始滑动到滑动结束,一直记录着当前滑动到的位置。float mOffset;//从一个页面滑动到另一个页面,记录着滑动的百分比。int mOffsetPx;//记录着从开始滑动的页面与当前状态的滑动。每次滑动结束之后,会被重置。}

ScrollEventAdapter总结:

(1)当调用ViewPager2的setAdapter方法时,此时应该回调一次dispatchSelected方法。
(2)当调用setCurrentItem(x, false)方法,不会调用onScrollStateChanged方法,于是不会产生idle状态,
   所以,咱们须要在onScrolled方法特殊处理(onScrolled方法会被调用)。
(3)正常的拖动和释放,就是onScrollStateChanged方法和onScrolled方法的正常回调。

3.PageTransformerAdapter

    void setPageTransformer(@Nullable PageTransformer transformer) {// TODO: add support for reverseDrawingOrder: b/112892792// TODO: add support for pageLayerType: b/112893074mPageTransformer = transformer; }@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {if (mPageTransformer == null) {return;}float transformOffset = -positionOffset;for (int i = 0; i < mLayoutManager.getChildCount(); i++) {View view = mLayoutManager.getChildAt(i);if (view == null) {throw new IllegalStateException(String.format(Locale.US,"LayoutManager returned a null child at pos %d/%d while transforming pages",i, mLayoutManager.getChildCount()));}int currPos = mLayoutManager.getPosition(view);float viewOffset = transformOffset + (currPos - position);mPageTransformer.transformPage(view, viewOffset);}}

PageTransformerAdapter实现也非常简单,PageTransformerAdapter实现于OnPageChangeCallback接口,监听的是ScrollEventAdapter的页面滑动事件,然后将页面滑动事件转换成为上面特殊的事件

4.FragmentStateAdapter

    //(1)key为itemId,value为Fragment。表示position与所放Fragment的对应关系(itemId与position有对应关系)final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();//(2)key为itemId,value为Fragment的状态private final LongSparseArray<Fragment.SavedState> mSavedStates = new LongSparseArray<>();//(3)key为itemId, value为ItemView的id。private final LongSparseArray<Integer> mItemIdToViewHolder = new LongSparseArray<>();private FragmentMaxLifecycleEnforcer mFragmentMaxLifecycleEnforcer;boolean mIsInGracePeriod = false;private boolean mHasStaleFragments = false;

1 onCreateViewHolder

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;}
}

创建FrameLayout视图

2 onBindViewHolder()

    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {final long itemId = holder.getItemId();final int viewHolderId = holder.getContainer().getId();final Long boundItemId = itemForViewHolder(viewHolderId); // 1 如果当前ItemView已经加载了Fragment,并且不是同一个Fragment// 那么就移除if (boundItemId != null && boundItemId != itemId) {removeFragment(boundItemId);mItemIdToViewHolder.remove(boundItemId);}mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry// 2 保证对应位置的Fragment已经初始化,并且放在mFragments中-->确定相应的Fragment,ensureFragment(position);final FrameLayout container = holder.getContainer();// 3 特殊情况处理,当RecyclerView让ItemView保持在Window,但是不在视图树中。if (ViewCompat.isAttachedToWindow(container)) {if (container.getParent() != null) {throw new IllegalStateException("Design assumption violated.");}container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {@Overridepublic void onLayoutChange(View v, int left, int top, int right, int bottom,int oldLeft, int oldTop, int oldRight, int oldBottom) {if (container.getParent() != null) {container.removeOnLayoutChangeListener(this);placeFragmentInViewHolder(holder);}}});}gcFragments();}private void ensureFragment(int position) {long itemId = getItemId(position);if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment = createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));mFragments.put(itemId, newFragment);}}

3 onViewAttachedToWindow (ViewHolder与Fragment进行绑定)

@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {placeFragmentInViewHolder(holder);gcFragments();
}

调用placeFragmentInViewHolder方法加载Fragment

4.onViewRecycled (ViewHolder与Fragment解除绑定)

    @Overridepublic final void onViewRecycled(@NonNull FragmentViewHolder holder) {final int viewHolderId = holder.getContainer().getId();final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VHif (boundItemId != null) {removeFragment(boundItemId);mItemIdToViewHolder.remove(boundItemId);}}

当ViewHolder被回收到回收池中,onViewRecycled方法会被调用 。

有在onViewDetachedFromWindow中去删除Fragment,而是在onViewRecycled中回收。因为当onViewRecycled方法被调用,表示当前ViewHolder已经彻底没有用了,被放入回收池,等待后面被复用,此时存在的情况可能有:(1).当前ItemView手动移除掉了;(2).当前位置对应的视图已经彻底不在屏幕中,被当前屏幕中某些位置复用了。所以在onViewRecycled方法里面移除Fragment比较合适 2.不在onViewDetachedFromWindow中删除的原因呢? 因为每当一个页面被滑走,都会调用这个方法,如果对其Fragment进行卸载,此时用户又滑回来,又要重新加载一次,这性能就下降了很多

5 .placeFragmentInViewHolder

 void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {// ······// 1.Fragment未添加到ItemView中,但是View已经创建// 非法状态if (!fragment.isAdded() && view != null) {throw new IllegalStateException("Design assumption violated.");}// 2.Fragment添加到ItemView中,但是View未创建// 先等待View创建完成,然后将View添加到Container。if (fragment.isAdded() && view == null) {scheduleViewAttach(fragment, container);return;}// 3.Fragment添加到ItemView中,同时View已经创建完成并且添加到Container中// 需要保证View添加到正确的Container中。if (fragment.isAdded() && view.getParent() != null) {if (view.getParent() != container) {addViewToContainer(view, container);}return;}// 4.Fragment添加到ItemView中,同时View已经创建完成但是未添加到Container中// 需要将View添加到Container中。if (fragment.isAdded()) {addViewToContainer(view, container);return;}// 5.Fragment未创建,View未创建、未添加if (!shouldDelayFragmentTransactions()) {scheduleViewAttach(fragment, container);mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).setMaxLifecycle(fragment, STARTED)  //懒加载.commitNow();mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);} else {// 调用了第5步,但是Fragment还未真正创建if (mFragmentManager.isDestroyed()) {return; // nothing we can do}mLifecycle.addObserver(new GenericLifecycleObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (shouldDelayFragmentTransactions()) {return;}source.getLifecycle().removeObserver(this);if (ViewCompat.isAttachedToWindow(holder.getContainer())) {placeFragmentInViewHolder(holder);}}});}}

最后总结

1 ViewPager2本身是一个ViewGroup,没有特殊作用,只是用来装一个RecyclerView。
2 PagerSnapHelper实现页面切换效果的原因是calculateDistanceToFinalSnap阻止RecyclerView的Fling事件,直接让它滑动相邻页面;findSnapView方法和findTargetSnapPosition用来辅助滑动到正确的位置。
3 ScrollEventAdapter的作用将RecyclerView的滑动事件转换成为ViewPager2的页面滑动事件。
4 PageTransformerAdapter的作用将普通的页面滑动事件转换为特殊事件。
5 FragmentStateAdapter完美实现了使用Adapter加载Fragment。在FragmentStateAdapter中,完美地考虑到ViewHolder的复用,Fragment加载和卸载。