本文参考了【进阶】RecyclerView源码解析(一)——绘制流程,有不少地方我是直接照搬过来的,侵权立删。
源码版本基于com.android.support:recyclerview-v7:27.1.1
RecyclerView的源码非常复杂,再加上一些辅助类其代码量超过了2W行,这也导致很多人望而却步,但是作为Android体系中一个非常重要的组件,学习其源码对于我们了解RecyclerView是如何运行的有很大的帮助。由于RecyclerView的源码实在太多,所以我们就从主要的流程开始,毕竟RecyclerView也是直接继承自ViewGroup,我们都知道自定义ViewGroup其主要流程在于重写onMeasure,onLayout以及onDraw三个方法,我们也先从这三个流程来入手。
onMesure
protected void onMeasure(int widthSpec, int heightSpec) {
//没有设置LayoutManager走默认的measure流程
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//这里LayoutManager的几个子类isAutoMeasureEnabled()方法默认为true
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//代码省略......
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
//如果测量模式是精确模式,直接返回,不继续测量
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
//mState.mLayoutStep默认值是State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
//measure前的一些准备工作
dispatchLayoutStep1();
//走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//实际measure发生的地方,这里执行完后mState.mLayoutStep变为State.STEP_ANIMATIONS
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
//判断是否需要执行二次测量,如果需要则再执行一次 dispatchLayoutStep2()
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
//代码省略......
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
整个measure的流程大致如上,中间删掉了一些代码,其实看源码我们没有必要去深究代码细节,只需要关注其整体流程即可。
我们从上往下逐个分:
//没有设置LayoutManager走默认的measure流程
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
mLayout就是我们为RecyclerView设置的LayoutManager,这里判断当我们没有设置LayoutManager时,走默认的测量。
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
//LayoutManager.chooseSize方法
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
chooseSize方法就是根据自身的测量模式(mode)来获取其相应的自身宽高,然后直接调用setMeasuredDimension方法设置自身宽高。由于这里没有进行child的测量,因此不难理解当我们没有设置LayoutManager时界面也就会出现什么都没有的情况。
然后判断mLayout.isAutoMeasureEnabled()。
//LinearLayoutManager
public boolean isAutoMeasureEnabled() {
return true;
}
这里默认为true(GridLayoutManager和StaggerLayoutManager与此类似),进入内部流程。
接下来判断测量模式,如果是精确测量模式则什么也不做,直接返回,否则进入下一步
final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
判断当前绘制步骤状态(默认是State.STEP_START,RecyclerView内部维护了一个叫State的类,便于管理RecyclerView相应的状态信息,这个后面再分析),执行相应操作。
//mState.mLayoutStep默认值是State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
//measure前的一些准备工作
dispatchLayoutStep1();
//走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
}
//RecyclerView的一个内部类,管理RecyclerView的状态信息
public static class State {
static final int STEP_START = 1;
static final int STEP_LAYOUT = 1 << 1;
static final int STEP_ANIMATIONS = 1 << 2;
......
@LayoutState
int mLayoutStep = STEP_START;
......
}
执行dispatchLayoutStep1()
方法,从这个方法的注释就可以看到,dispatchLayoutStep1()
仅仅只是做一些准备工作,包括一些状态的清理及初始化,具体的细节就不分析了。
/**
* The first step of a layout where we;
* - process adapter updates
* - decide which animation should run
* - save information about current views
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
......
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
......
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
//方法执行完后,将mState.mLayoutStep设置为State.STEP_LAYOUT,便于下一步操作
mState.mLayoutStep = State.STEP_LAYOUT;
}
执行dispatchLayoutStep2()
,这里也是真正执行子view布局的地方,可以看到RecyclerView将layout工作转交给了LayoutManager。
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
//获取子view的数量,这里调用了adapter的getItemCount()方法。
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//实际执行子view布局的地方,这里是将measure工作转交给LayoutManager。
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
//测量工作完成后,修改State的layoutStep为State.STEP_ANIMATIONS,
//为后续子view显示或退出显示动画做准备
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
可以看到,实际执行子元素布局的工作转交给了LayoutManager,其具体实现在LayoutManager的子类中,这里以LinearLayoutManager为例来分析。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
//寻找锚点
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
//填充子view,从锚点往上和从锚点往下
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
//RecyclerView滑动时填充需要的布局
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
//代码省略......
ensureLayoutState();
mLayoutState.mRecycle = false;
//判断绘制方向,默认是垂直布局或者LayoutDirection是从左往右
// resolve layout direction
resolveShouldLayoutReverse();
final View focused = getFocusedChild();
//如果锚点信息无效则重置锚点信息
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
//代码省略......
final int firstLayoutDirection;
//判断布局方向,mAnchorInfo.mLayoutFromEnd默认为false
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
}
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
//如果是倒着布局的话
// fill towards start
//从锚点往上布局
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
//从锚点往下布局
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
//如果还有多余的填充像素数,再次运行fill方法
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
//默认正常布局,先从锚点往下布局,在往上布局
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
//与上面类似,如果有多余的绘制,则再次调用fill方法
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
// changes may cause gaps on the UI, try to fix them.
// TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
// changed
//这里是修复由于子元素滑动可能会导致出现的一些空白
if (getChildCount() > 0) {
// because layout from end may be changed by scroll to position
// we re-calculate it.
// find which side we should check for gaps.
if (mShouldReverseLayout ^ mStackFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
} else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
//完成后重置相关的一些参数
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
//代码省略......
}
子元素具体的布局是在fill方法中。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
//代码省略......
}
//代码省略......
return start - layoutState.mAvailable;
}
具体的逻辑都在layoutChunk(recycler, state, layoutState, layoutChunkResult)
方法里面。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//获取下一个子view
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
//根据view的状态执行对应的addview方法
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//测量从LayoutState中取出的子view
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
//计算left, top, right, bottom;
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
//完成子view的布局
layoutDecoratedWithMargins(view, left, top, right, bottom);
//代码省略......
}
首先看下这行代码View view = layoutState.next(recycler);
,这里非常重要。
View next(RecyclerView.Recycler recycler) {
//在第一次加载RecyclerView的时候mScrapList为null,所以暂时不考虑
if (mScrapList != null) {
return nextViewFromScrapList();
}
//获取view,这里使用了缓存策略
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
继续看recycler.getViewForPosition(mCurrentPosition);
这个方法。
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
//代码省略......
ViewHolder holder = null;
//省略掉从缓存中获取view的代码......
//代码省略......
if (holder == null) {
//代码省略......
holder = mAdapter.createViewHolder(RecyclerView.this, type);
//代码省略......
}
}
//代码省略......
return holder;
}
这里获取view最终调用ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)
方法,并在里面进行了一堆判断,主要是判断scrap、cache以及RecyclerViewPool中是否有缓存,如果这三个缓存中都没有缓存的view,最后才调用adapter的createViewHolder方法创建一个viewholder,在createViewHolder方法中最终会调用我们自己实现的onCreateViewHolder方法。注意RecyclerView这里缓存的是ViewHolder,这也是RecyclerView与ListView不同的地方之一。
对View view = layoutState.next(recycler);
的分析暂时告一段落,沿着layoutChunk
方法继续往下分析。
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
这里就是将我们通过layoutState.next(recycler)
获取的view添加到RecyclerView中,继续往下。
//测量获取的子view
//LinearLayoutManager->layoutChunk
measureChildWithMargins(view, 0, 0);
//RecyclerView->LayoutManager->measureChildWithMargins
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//这里获取Itemdecoration里面设置的水平方向和竖直方向的偏移量
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
//获取子view的measureSpec
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
//调用child.measure方法测量子view自身宽高
child.measure(widthSpec, heightSpec);
}
}
这个方法主要是获取childView上下左右的偏移量以及完成childView自身的测量。这里分析下mRecyclerView.getItemDecorInsetsForChild(child);
方法。
Rect getItemDecorInsetsForChild(View child) {
//代码省略......
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
//mItemDecorations是保存ItemDecoration的集合,
//我们给RecyclerView绘制分割线等诸多view的装饰效果都是通过继承ItemDecoration来实现的
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//调用ItemDecoration的getItemOffsets方法来获取相应的view偏移量
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
//代码省略......
return insets;
}
这个方法里面循环调用了ItemDecoration的getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
方法(RecyclerView可以设置多个ItemDecoration),并将我们设置的left,top,right,bottom偏移量设置到outRect中去,最后得到RecyclerView设置的所有ItemDecoration的left,top,right,bottom偏移量。这个方法我们在继承ItemDecoration的时候需要我们自己去实现,这里简单说下偏移量,用一张图片来表示:
分析完了measureChildWithMargins后,继续往下。
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
这里是设置RecyclerView中各个子view应该布局的位置(left,top,right,bottom),设置完了后调用layoutDecoratedWithMargins(view, left, top, right, bottom);
开始布局子view。
LinearLayoutManager->layoutChunk->layoutDecoratedWithMargins
layoutDecoratedWithMargins(view, left, top, right, bottom);
RecyclerView->LayoutManager->layoutDecoratedWithMargins
public void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
可以看到,在layoutDecoratedWithMargins
方法中,子view的布局位置是由left,top,right,bottom和ItemDecoration设置的偏移量共同决定的。至此onMeasure的流程就分析完了,接下来看下onLayout方法。
onLayout
在分析onMeasure的时候有几个问题:
- 1、为什么当不设置LayoutManager时,界面什么都没有?
答:因为RecyclerView把子view的测量以及布局交给了LayoutManager来完成,当我们不设置LayoutManager时,RecyclerView无法完成子view的测量和布局,自然也就无法显示到界面上。 - 2、当我们给RecyclerView设置精确宽高度时,onMeasure不也是直接就返回,没有经过LayoutManager的测量和布局吗,为什么最后能显示子view呢?
答:当给RecyclerView设置精确宽高值时,LayoutManager对子view的测量和布局工作是在onLayout方法中完成的。这也是接下来将要分析的部分。 - 3、在执行完
dispatchLayoutStep2();
方法后,设置了mState.mLayoutStep = State.STEP_ANIMATIONS;
,这个STEP_ANIMATIONS又是在哪里调用的呢?
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
void dispatchLayout() {
//代码省略......
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
可以看到在dispatchLayout
方法中,仍然会判断State的mLayoutStep这个变量,当给RecyclerView设置精确宽高值是,在onMeasure方法中直接返回,跳过了接下来的步骤,因此mLayoutStep仍然是其初始值,也就是State.STEP_START,因此条件成立并执行dispatchLayoutStep1();
和dispatchLayoutStep2();
完成子view的测量和布局。
这里还有一个判断,也就是当我们在布局的时候改变了RecyclerView的宽高值,也会调用dispatchLayoutStep2();
方法重新测量和布局。
在dispatchLayout()方法的最后调用了dispatchLayoutStep3();
,这里就可以回答上面的第三个问题,也就是STEP_ANIMATIONS就是在这个方法中判断的。根据注释,我们可以看出,这个方法主要是保存子view的一些动画以及做一些相关的清理工作。
private void dispatchLayoutStep3() {
//这里有一个前提就是mLayoutStep必须是State.STEP_ANIMATIONS,
//也就是执行完了dispatchLayoutStep2()后该方法才能继续往下走
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
//这里将mLayoutStep又重置为了State.STEP_START
mState.mLayoutStep = State.STEP_START;
//如果设置了ItemAnimator,这里判断成立,RecyclerView预置了默认的ItemAnimator
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
// traverse list in reverse because we may call animateChange in the loop which may
// remove the target view holder.
//循环保存ItemAnimator中设置的动画信息,如果有设置的话,RecyclerView中预置了默认动画
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
//代码省略......
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
// we add and remove so that any post info is merged.
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
//执行ItemAnimator中设置的动画
mViewInfoStore.process(mViewInfoProcessCallback);
}
//layout完成后,执行一些清理工作
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mDispatchItemsChangedEvent = false;
mState.mRunSimpleAnimations = false;
//代码省略......
}
这里看下mViewInfoStore.process(mViewInfoProcessCallback);
这个方法。
mViewInfoStore.process(mViewInfoProcessCallback);
/**
* The callback to convert view info diffs into animations.
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
//代码省略......
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
animateAppearance(viewHolder, preInfo, info);
}
//代码省略......
};
void animateAppearance(@NonNull ViewHolder itemHolder,
@Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
itemHolder.setIsRecyclable(false);
//判断是否设置了动画,如果设置了,则执行动画
if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
//post动画执行
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
//调用ItemAnimator的runPendingAnimations()方法执行动画,该方法需要我们自己实现
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
可以看到,所有动画逻辑都封装在了mViewInfoProcessCallback中,这个callback是ViewInfoStore类的一个内部接口,里面封装了各种动画行为。
interface ProcessCallback {
void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@Nullable ItemHolderInfo postInfo);
void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
ItemHolderInfo postInfo);
void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo);
void unused(ViewHolder holder);
}
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
// Appeared then disappeared. Not useful for animations.
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// Set as "disappeared" by the LayoutManager (addDisappearingView)
if (record.preInfo == null) {
// similar to appear disappear but happened between different layout passes.
// this can happen when the layout manager is using auto-measure
callback.unused(viewHolder);
} else {
callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
}
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// Appeared in the layout but not in the adapter (e.g. entered the viewport)
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
// Persistent in both passes. Animate persistence
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// Was in pre-layout, never been added to post layout
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// Was not in pre-layout, been added to post layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
} else if (DEBUG) {
throw new IllegalStateException("record without any reasonable flag combination:/");
}
InfoRecord.recycle(record);
}
}
mViewInfoStore.process(mViewInfoProcessCallback);
方法内部就是各种判断,并调用相应的动画执行。至此,onLayout方法的分析也告一段落,我们接着分析onDraw方法。
onDraw
在分析onDraw之前,首先了解下View的绘制流程。View的draw方法太长了,这里就不拿出来分析,我们只了解下流程,通过其源码我们可以得出大致的流程:
draw -> drawbackground(绘制背景,不能重写) -> onDraw(绘制自身) -> dispatchDraw(绘制子view)
-> onDrawForeground(绘制前景) -> drawDefaultFocusHighlight(绘制其他)
RecyclerView继承了ViewGroup,内部重写了draw,onDraw这两个方法,我们就来分析下:
public void draw(Canvas c) {
//调用父类的draw方法绘制自身
//这里会依次调用上面所说的流程,完成绘制
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
//绘制悬浮效果
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
// TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
// need find children closest to edges. Not sure if it is worth the effort.
//判断是否需要重绘
boolean needsInvalidate = false;
if (mLeftGlow != null && !mLeftGlow.isFinished()) {
//......
needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
c.restoreToCount(restore);
}
if (mTopGlow != null && !mTopGlow.isFinished()) {
//......
}
if (mRightGlow != null && !mRightGlow.isFinished()) {
//......
}
if (mBottomGlow != null && !mBottomGlow.isFinished()) {
//......
}
// If some views are animating, ItemDecorators are likely to move/change with them.
// Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
// display lists are not invalidated.
//如果有子view的动画还在执行,则需要重绘
if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
&& mItemAnimator.isRunning()) {
needsInvalidate = true;
}
//如果需要重绘,发送重绘信息
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
先看下draw方法,首先调用super.draw(canvas c)
,完成自身的绘制(这里会调用onDraw方法,完成divider的绘制);然后调用ItemDecoration的onDrawOver(需要我们自己实现)来绘制子view的悬浮效果,最后进行一些列判断,判断是否需要重绘。
接着看onDraw方法,这里首先调用super.onDraw(c);
来绘制。然后调用ItemDecoration的onDraw方法来绘制分割线等效果,这个onDraw也是需要我们自己去实现的。
至此RecyclerView的整个绘制流程就分析完了,由于这里只侧重整体的流程,有很多详细的地方没有分析到,如有兴趣可自行分析细节。总结下几个关键点:
1、RecyclerView将子view的measure工作交给了LayoutManager来完成的,如果没有设置LayoutManager,则列表不会有任何显示。
2、RecyclerView的子view绘制分为正序绘制和倒序绘制。首先是确定锚点,然后由锚点分别向两边绘制,因此fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable)
方法至少会执行两次,如果绘制完了还有剩余空间,则继续执行fill方法。
3、fill方法的核心逻辑在于layoutChunk方法中,layoutChunk方法中首先会通过layoutState.next(recycler)
获取view,首先是从scrap、cache以及RecyclerViewPool等缓存中获取,如果这些缓存中都没有,则调用adapter的createViewHolder方法创建一个ViewHolder
4、如果没有为RecyclerView设置精确宽高值,则onMeasure方法会完成子view的测量以及布局(如有设置ItemDecoration,则会将ItemDecoration的getItemOffsets方法中设置的left,top,right,bottom偏移量也算在内),在onLayout方法中仅仅只需要完成一些清理工作以及子view进出动画的处理工作;如果设置了精确的宽高值,则对子view的测量以及布局工作会在onLayout中完成。
5、RecyclerView重写了draw和onDraw方法,如果设置了ItemDecoration,并重写了onDrawOver以及onDraw方法,则在draw方法中会调用ItemDecoration的onDrawOver绘制悬浮效果;在onDraw方法中调用ItemDecoration的onDraw方法绘制子view之间的分割线以及其他装饰效果。