1.ViewPager2的新特性
简单说下viewPager2的新特征,相信大家都知道基于RecyclerView实现
1.基于RecyclerView实现。这意味着RecyclerView的优点将会被ViewPager2所继承。
2.支持竖直滑动。只需要一个参数就可以改变滑动方向。
3.支持关闭用户输入。通过setUserInputEnabled来设置是否禁止用户滑动页面。
4.支持通过编程方式滚动。通过fakeDragBy(offsetPx)代码模拟用户滑动页面。
5.CompositePageTransformer 支持同时添加多个PageTransformer。
6.支持DiffUtil ,可以添加数据集合改变的item动画。
7.支持RTL (right-to-left)布局。
2.ViewPager2的使用及注意点
使用的话非常简单,跟使用RecyclerView一样~ 下面链接官方文档:
常用方法:
setAdapter() 设置适配器
setOrientation() 设置布局方向
setCurrentItem() 设置当前Item下标
beginFakeDrag() 开始模拟拖拽
fakeDragBy() 模拟拖拽中
endFakeDrag() 模拟拖拽结束
setUserInputEnabled() 设置是否允许用户输入/触摸
setOffscreenPageLimit()设置屏幕外加载页面数量
registerOnPageChangeCallback() 注册页面改变回调
setPageTransformer() 设置页面滑动时的变换效果
(1)关联Tablayout,需要使用TabLayoutMediator在构造方法传入TabLayout和ViewPager
(2)ViewPager2新增了一个fakeDragBy的方法。通过这个方法可以来模拟拖拽。在使用fakeDragBy前需要先beginFakeDrag方法来开启模拟拖拽。
fakeDragBy会返回一个boolean值,true表示有fake drag正在执行,而返回false表示当前没有fake drag在执行。我们通过代码来尝试下:
fun fakeDragBy(view: View) {
viewPager2.beginFakeDrag()
if (viewPager2.fakeDragBy(-310f))
viewPager2.endFakeDrag()
}
需要注意到是fakeDragBy接受一个float的参数,当参数值为正数时表示向前一个页面滑动,当值为负数时表示向下一个页面滑动。
https://developer.android.com/training/animation/vp2-migration
3.ViewPager2源码分析
看下ViewPager初始化的地方
private void initialize(Context context, AttributeSet attrs) {
// .... 省约部分
// 初始化RecyclerView
mRecyclerView = new RecyclerViewImpl(context);
mRecyclerView.setId(ViewCompat.generateViewId());
// 初始化LayoutManager
mLayoutManager = 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事件能滑动到位置。
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方法。此时RecyclerView的滑动状态为RecyclerView.SCROLL_STATE_IDLE,所以就会分别调用findSnapView方法来找到需要显示在RecyclerView的最前面的View。找到目标View之后,就会调用calculateDistanceToFinalSnap方法来计算需要滑动的距离,然后调动RecyclerView相关方法进行滑动。
当RecyclerView在Fling时,如果想要不去拦截Fling时间,想让RecyclerView开心的Fling,可以直接在findTargetSnapPosition方法返回RecyclerView.NO_POSITION即可,或者我们可以在findTargetSnapPosition方法来计算滑动的最终位置,然后通过SmoothScroller来实现滑动。因为viewPager2是不支持快速滑动Filing事件的,所以在PagerSnapHelper中的findTargetSnapPosition()方法中做了处理下面看看
(1)findTargetSnapPosition() 看看怎么阻止recyclerView的Filing事件
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
// ······
// 找到与当前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 best
distanceBefore = distance;
closestChildBeforeCenter = child;
}
if (distance >= 0 && distance < distanceAfter) {
// Child is after the center and closer then the previous best
distanceAfter = distance;
closestChildAfterCenter = child;
}
}
// 根据滑动的方向来返回的相应位置
final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);
if (forwardDirection && closestChildAfterCenter != null) {
return layoutManager.getPosition(closestChildAfterCenter);
} else if (!forwardDirection && closestChildBeforeCenter != null) {
return layoutManager.getPosition(closestChildBeforeCenter);
}
// 最后兜底计算
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的效果。上面贴过snapFromFling()方法的代码
(2)findSnapView() 确定最终位置的ItemView
当recyclerView滑动完之后,就会调用findSnapView这个方法,来确认最终位置的ItemView
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;
}
在findSnapView内部,调用findCenterView方法,我们先来看看findCenterView方法的
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;
}
上面代码也简单,找到当前中心距离屏幕中心最近的ItemView。这个怎么来理解呢?比如说,我们手指在滑动一个页面,滑动到一定距离时就松开了,此时屏幕当中有两个页面,那么ViewPager2应该滑动到哪一个页面呢?当然是距离屏幕中心最近的页面。findCenterView方法的作用便是如此。
(3)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;
}
最终掉到distanceToCenter()
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
(4)PagerSnapHelper总结
PagerSnapHelper可以实现页面切换的效果,主要如下几点:
(1)首先阻止RecyclerView的Fling事件,阻止的方式就是重写findTargetSnapPosition方法,当RecyclerView触发了Fling事件之后,直接滑动到下一个或者上一个。
(2)如果RecyclerView没有触发Fling事件,或者Fling阶段未能滑动到正确位置,此时需要findSnapView方法和calculateDistanceToFinalSnap来保证滑动到正确的页面。
2.ScrollEventAdapter分析->将recyclerView的滚动事件转换成viewPager2的事件
//表示当前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()
当RecyclerView的滑动状态发生变化,这个方法就会被调用
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
// 1. 开始拖动
if ((mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG
|| mScrollState != SCROLL_STATE_DRAGGING)
&& newState == RecyclerView.SCROLL_STATE_DRAGGING) {
startDrag(false);
return;
}
// 2. 拖动手势的释放
if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_SETTLING) {
// Only go through the settling phase if the drag actually moved the page
if (mScrollHappened) {
dispatchStateChanged(SCROLL_STATE_SETTLING);
mDispatchSelected = true;
}
return;
}
//3.滑动结束
// 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();
}
}
}
上面大概分三步:
(1)开始拖动,会调用startDrag方法表示拖动开始。
(2)拖动手势的释放,此时ViewPager2会准备滑动到正确的位置。
(3)滑动结束,此时ScrollEventAdapter会调用相关的方法更新状态。
2.onScrolled()
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
mScrollHappened = true;
// 更新且计算当前position和offset的值
updateScrollEventValues();
if (mDispatchSelected) {
// 拖动手势释放,ViewPager2正在滑动到正确的位置
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);
// 因为调用了setCurrentItem(x, false)不会触发IDLE状态的产生,所以需要在这里
// 调用dispatchStateChanged方法
if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)
&& mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {
dispatchStateChanged(SCROLL_STATE_IDLE);
resetState();
}
}
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/112893074
mPageTransformer = transformer;
}
@Override
public 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的页面滑动事件,然后将页面滑动事件转换成为上面特殊的事件 下面说说viewPager2为我们提供的2个PageTransformer的实现类
(1)MarginPageTransformer
PageTransformer是一个位于ViewPager2中的接口,
(1)ViewPager2移除了setPageMargin方法,那么怎么为ViewPager2设置页面间距呢?
ViewPager2中为我们提供了MarginPageTransformer,我们可以通过ViewPager2的setPageTransformer方法来设置页面间距。
viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt())
(2)CompositePageTransformer
ViewPager2设置了页面间距后如果还想设置页面动画的Transformer怎么办呢?这是时候就需要用到CompositePageTransformer,因为CompositePageTransformer实现了PageTransformer接口,同时在其内部维护了一个List集合,我们可以将多个PageTransformer添加到CompositePageTransformer中。
public final class CompositePageTransformer implements PageTransformer {
private final List mTransformers = new ArrayList<>();
public void addTransformer(@NonNull PageTransformer transformer) {
mTransformers.add(transformer);
}
public void removeTransformer(@NonNull PageTransformer transformer) {
mTransformers.remove(transformer);
}
@Override
public void transformPage(@NonNull View page, float position) {
for (PageTransformer transformer : mTransformers) {
transformer.transformPage(page, position);
}
}
}
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(ScaleInTransformer())
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)
上述代码中我们通过CompositePageTransformer为ViewPager设置了MarginPageTransformer和一个页面缩放的ScaleInTransformer。当然还可以定制不同的页面动画只需要实现PageTransformer接口,然后放到一起即可
4.FragmentStateAdapter
下面简单说下fragmentStateAdapter与ViewPager2的配合,看下几个变量含义,然后看adapter的onCreateViewHolder()和onBindViewHolder()
//(1)key为itemId,value为Fragment。表示position与所放Fragment的对应关系(itemId与position有对应关系)
final LongSparseArray mFragments = new LongSparseArray<>();
//(2)key为itemId,value为Fragment的状态
private final LongSparseArray mSavedStates = new LongSparseArray<>();
//(3)key为itemId, value为ItemView的id。
private final LongSparseArray 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);
// 如果当前ItemView已经加载了Fragment,并且不是同一个Fragment
// 那么就移除
if (boundItemId != null && boundItemId != itemId) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
// 保证对应位置的Fragment已经初始化,并且放在mFragments中-->确定相应的Fragment,
ensureFragment(position);
final FrameLayout container = holder.getContainer();
// 特殊情况处理,当RecyclerView让ItemView保持在Window,
// 但是不在视图树中。
if (ViewCompat.isAttachedToWindow(container)) {
if (container.getParent() != null) {
throw new IllegalStateException("Design assumption violated.");
}
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public 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 Fragment
Fragment newFragment = createFragment(position);
newFragment.setInitialSavedState(mSavedStates.get(itemId));
mFragments.put(itemId, newFragment);
}
}
(1)如果当前ItemView上已经加载了Fragment,并且不是同一个Fragment(ItemView被复用了),那么先移除掉ItemView上的Fragment。
(2)ensureFragment()初始化Fragment视图
(3)如果存在特殊情况,会走特殊情况。正常来说,都会经过onAttachToWindow方法来对Fragment进行加载。
(3).onViewAttachedToWindow (ViewHolder与Fragment进行绑定)
@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
placeFragmentInViewHolder(holder);
gcFragments();
}
同样的,调用了placeFragmentInViewHolder方法加载Fragment。placeFragmentInViewHolder这个方法的代码放到最后贴出来说
(4).onViewRecycled (ViewHolder与Fragment解除绑定)
当ViewHolder被回收到回收池中,onViewRecycled方法会被调用,来看看代码
@Override
public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
final int viewHolderId = holder.getContainer().getId();
final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
if (boundItemId != null) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
}
这里重要的一点是:没有在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() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (shouldDelayFragmentTransactions()) {
return;
}
source.getLifecycle().removeObserver(this);
if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
placeFragmentInViewHolder(holder);
}
}
});
}
}
这里说下viewPager2的Lifecycle跟ViewPager一样默认是(setMaxLifecycle(fragment, STARTED) 懒加载),然后滑动界面时设置为Resume,代码如下
public void onPageScrollStateChanged(int state) {
updateFragmentMaxLifecycle(false);
}
5.总结
(1)ViewPager2本身是一个ViewGroup,没有特殊作用,只是用来装一个RecyclerView。
(2)PagerSnapHelper实现页面切换效果的原因是calculateDistanceToFinalSnap阻止RecyclerView的Fling事件,直接让它滑动相邻页面;findSnapView方法和findTargetSnapPosition用来辅助滑动到正确的位置。
(3)ScrollEventAdapter的作用将RecyclerView的滑动事件转换成为ViewPager2的页面滑动事件。
(4)PageTransformerAdapter的作用将普通的页面滑动事件转换为特殊事件。
(5)FragmentStateAdapter完美实现了使用Adapter加载Fragment。在FragmentStateAdapter中,完美地考虑到ViewHolder的复用,Fragment加载和卸载。
推荐文章:
【SnapHelper 讲解】,方便理解ViewPager2里的PagerSnapHelper
https://hitendev.github.io/2019/04/24/SnapHelper%E7%A1%AC%E6%A0%B8%E8%AE%B2%E8%A7%A3/
【ViewPager2避坑系列】瞬间暴增数个Fragment::
https://hitendev.github.io/2019/05/19/%E3%80%90ViewPager2%E9%81%BF%E5%9D%91%E7%B3%BB%E5%88%97%E3%80%91%E7%9E%AC%E9%97%B4%E6%9A%B4%E5%A2%9E%E6%95%B0%E4%B8%AAFragment/