RecyclerView测量和布局分析

作者: CP9 | 来源:发表于2017-12-20 17:59 被阅读479次

在RecyclerView从测量到布局的过程中必会经过的三个流程:
dispatchLayoutStep1->dispatchLayoutStep2->dispatchLayoutStep3

dispatchLayoutStep1

保存当前的子View的一些信息

mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();

保存当前适配器的获取焦点的item,如果该item被删除,则转移到被删除的item的前一个
saveFocusInfo

processAdapterUpdatesAndSetAnimationFlags —— 当适配器更新,决定想要执行的动画类型

  1. 如果你的LayoutManager支持predictive动画(根据你的LayoutManagersupportsPredictiveItemAnimations方法返回判断是否支持predictive动画),则会调用AdapterHelperpreProcess方法来选择动画的类型,不过在这一步并不执行动画,只是预处理
  2. 赋值mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations

根据上一步得到的mState.mRunSimpleAnimations,如果为true,则将RecyclerView中已存在的子View添加到ViewInfoStore中

if (mState.mRunSimpleAnimations) {
    int count = mChildHelper.getChildCount();
    for (int i = 0; i < count; ++i) {
          ...
          mViewInfoStore.addToPreLayout(holder, animationInfo);
    ...
}

根据上一步得到的mState.mRunPredictiveAnimations,如果为true,则调用子类LayoutManager的onLayoutChildren方法,找到是否有需要消失的子View

mLayout.onLayoutChildren(mRecycler, mState);
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
    ...
    if (wasHidden) {
        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
    } else {
        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
    }
    ...
}

具体可以查看下一期的RecyclerView的动画

dispatchLayoutStep2

为最终状态的子View进行真正的布局,主要依靠子类LayoutManager的onLayoutChildren方法,以LinearLayoutManager为例:

LinearLayoutManager的onLayoutChildren方法

找到布局的锚点和方向 —— updateAnchorInfoForLayout

这个锚点代表着RecyclerView的子View从哪个位置根据得到的方向开始布局

  1. 如果存在待定的滚动位置或已保存的状态,则从中更新锚点信息
updateAnchorFromPendingData(state, anchorInfo)
  1. 通过updateAnchorFromChildren从RecyclerView中已添加的子View中找到锚点View
// 1. 优先考虑获得焦点的子View
final View focused = getFocusedChild();
...
// 2. 其次考虑靠近起始点和结束点的有效的子View
View referenceChild = anchorInfo.mLayoutFromEnd
        ? findReferenceChildClosestToEnd(recycler, state)
        : findReferenceChildClosestToStart(recycler, state);
  1. 锚点位置根据mStackFromEnd选择0还是count-1
anchorInfo.assignCoordinateFromPadding();
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;

分离RecyclerView中的所有子View,并将它们添加到Scrap缓存或Cache缓存中 ——detachAndScrapAttachedViews

for (int i = childCount - 1; i >= 0; i--) {0
    final View v = getChildAt(i);
    scrapOrRecycleView(recycler, i, v);
}

根据第一步找锚点时得到的方向来填充

if (mAnchorInfo.mLayoutFromEnd) {
    // fill towards start
    updateLayoutStateToFillStart(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
    // fill towards end
    updateLayoutStateToFillEnd(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
}else{
    // fill towards end
    updateLayoutStateToFillEnd(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
    // fill towards start
    updateLayoutStateToFillStart(mAnchorInfo);
    fill(recycler, mLayoutState, state, false);
}

填充布局 —— fill

计算填充空间
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
while循环通过layoutChunk方法布局子View,直到填充空间不足
// 1. layoutState.mInfinite 表示是否还有足够的空间允许摆放View
// 2. remainingSpace > 0 表示剩余空间是否大于0
// 3. layoutState.hasMore(state) 表示当前的mCurrentPosition是否大于0,且小于mItemCount
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)){
      layoutChunk(recycler, state, layoutState, layoutChunkResult);
      ...
      if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
      layoutState.mAvailable -= layoutChunkResult.mConsumed;
      // 每次填充完子View计算所剩的填充空间(即每次减去填充的布局所消耗的空间)
      remainingSpace -= layoutChunkResult.mConsumed;
      }
      // 将超出边界的子View回收到Cache缓存或RecyclerViewPool中
      recycleByLayoutState(recycler, layoutState);
}
添加并摆放子View —— layoutChunk
  1. 通过Recycler的getViewForPosition方法获得当前mCurrentPosition对应的itemview
View view = layoutState.next(recycler);
  1. 根据layoutState的状态和是否翻转决定是顺序添加子View还是倒序,addView的过程中,会判断如果这个holder是来自Scrap缓存,则会调用unScrap方法将此holder从Scrap缓存中移除
if (mShouldReverseLayout == (layoutState.mLayoutDirection
        == LayoutState.LAYOUT_START)) {
    addView(view);
} else {
    addView(view, 0);
}
  1. 测量子View并将子View所占的高度记录到LayoutChunkResult中
measureChildWithMargins
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
  1. 摆放子View,调用子View的layout方法
layoutDecoratedWithMargins(view, left, top, right, bottom);

dispatchLayoutStep3

根据dispatchLayoutStep1中得到的mState.mRunSimpleAnimations值,如果为true,则处理适配器改变的动画

if (mState.mRunSimpleAnimations) {
      ...
      mViewInfoStore.addToPostLayout(holder, animationInfo);
...
      mViewInfoStore.process(mViewInfoProcessCallback);
}

主要通过ViewInfoStore的process处理动画,具体可以查看下一期的RecyclerView的动画

回收Scrap缓存中的holder,添加到Cache缓存或RecyclerViewPool中,具体可查看ReclerView的缓存分析

mLayout.removeAndRecycleScrapInt(mRecycler);

清除一些状态

例如StateInfo的两个和动画有关的变量

mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;

清除ViewInfoStore和焦点信息

mViewInfoStore.clear();
resetFocusInfo();

清除Scrap缓存中的mChangedScrap列表

if (mRecycler.mChangedScrap != null) {
    mRecycler.mChangedScrap.clear();
}

RecyclerView的测量和布局分析

如果我们的RecyclerView在xml中的宽高如下设置:

android:layout_width="match_parent"
android:layout_height="match_parent"

测量布局的流程如下图:


RecyclerView_测量布局1.png

如果我们的RecyclerView在xml中的宽高如下设置:

android:layout_width="wrap_content"
android:layout_height="match_parent"

只要宽高有一个为wrap_content,测量布局的流程如下图:

RecyclerView_测量和布局2.png

相关文章

网友评论

    本文标题:RecyclerView测量和布局分析

    本文链接:https://www.haomeiwen.com/subject/mczpbxtx.html