我们都知道在Android ViewGroup的绘制流程中,需要通过重写方法onMeasure(int width,int height) 和 onLayout(int l,int t,int r,int b)来实现自定定义ViewGroup的。
RecyclerView的测量、绘制依然是依靠这两个方法来实现的。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
//LinearLayoutManager默认开启'自动测量'
//onMeasure会执行两次
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//通过mLayout设置大小参数给recyclerview
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
//如果RecyclerView的宽高都是精确模式(Match_Parent或者精确的dp),则结束本方法的执行,接下来等待onLayout方法的执行
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
//进行第一步绘制(后面会详细讲到)
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
//更新宽高信息到mLayout对象,mLayout是一个LayoutManager的实例
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//进行真正的绘制布局操作
dispatchLayoutStep2();
//根据children的布局大小再做一次,宽高决策
//如果是精确,则取精确对应的值
//如果是AT_MOST,则取 size和(disired,min中的最大值)的最小值
// 否则 disired,min中的最大值)
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
//如果ReyclerView没有精确的宽高,且它的children中至少有一个item宽和高不精确(针对LinearLayoutManager)
//则需要进行二次测量
if (mLayout.shouldMeasureTwice()) {
//RecyclerView先将自己的宽高设置为精确模式
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
//再进行二次布局
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
...
}
}
/**
* 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);
//记录需要滚动的距离到State中
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
//优化requestLayout的调用
startInterceptRequestLayout();
//记录View动画信息的数据结果,清除缓存信息
mViewInfoStore.clear();
//标记滚动或者布局阶段,在此期间 某些方法不能被调用
onEnterLayoutOrScroll();
//处理Adapter更新内容和设置动画标记
processAdapterUpdatesAndSetAnimationFlags();
//保存焦点信息
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
//记录目前最大最小position
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
//processAdapterUpdatesAndSetAnimationFlags方法中设置的标记
//是否需要执行动画,该值在processAdapterUpdatesAndSetAnimationFlags()方法中进行设置
if (mState.mRunSimpleAnimations) {
//下一篇介绍动画相关内容
....
}
//processAdapterUpdatesAndSetAnimationFlags方法中设置的标记
//是否需要执行预测动画,该值在processAdapterUpdatesAndSetAnimationFlags()方法中进行设置
if (mState.mRunPredictiveAnimations) {
//下一篇介绍动画相关内容
...
//进行原有位置信息的清除操作
clearOldPositions();
} else {
//进行原有位置信息的清除操作
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
//设置状态为“布局”阶段
mState.mLayoutStep = State.STEP_LAYOUT;
}
/**
* 真正的布局child的操作
*/
private void dispatchLayoutStep2() {
...
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
//将更新操作一次性 触发
mAdapterHelper.consumeUpdatesInOnePass();
//设置一下mState参数信息,便于在布局阶段使用
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
//进入布局阶段
mState.mInPreLayout = false;
//真正的布局操作,交由LayoutManager去实现,比如LinearLayoutManager
mLayout.onLayoutChildren(mRecycler, mState);
...
}
咱们接着上面的内容,来看一下LinearLayoutManager的是如何布局RecyclerView的children的。
/**
* LinearLayoutManager.java
*/
@Override
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.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
final View focused = getFocusedChild();
//计算anchor的位置和坐标信息
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
//已占用空间和布局方向
calculateExtraLayoutSpace(state, mReusableIntPair);
int extraForStart = Math.max(0, mReusableIntPair[0])
+ mOrientationHelper.getStartAfterPadding();
int extraForEnd = Math.max(0, mReusableIntPair[1])
+ mOrientationHelper.getEndPadding();
//锚点位置确定
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
//暂时回收attchedView
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
mLayoutState.mNoRecycleSpace = 0;
if (mAnchorInfo.mLayoutFromEnd) {
....
} else {
//以mAnchorInfo为参照物 向后布局
//将mAnchorInfo传递给mLayoutState对象,便于布局
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
//开始布局
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
//以mAnchorInfo为参照物 向前布局
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
...
//预布局动画(在下一篇会详细介绍)
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
if (DEBUG) {
validateChildOrder();
}
}
/**
* LinearLayoutManager.java
*/
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;
//循环布局
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//布局item
layoutChunk(recycler, state, layoutState, layoutChunkResult);
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
//重新计算剩余空间
remainingSpace -= layoutChunkResult.mConsumed;
}
}
return start - layoutState.mAvailable;
}
/**
* LinearLayoutManager.java
*/
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//获取一个ItemView
//注意这里 实现了复用操作,具体的复用逻辑,再下一小节分析
View view = layoutState.next(recycler);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
//如果mScrapList为空,则正常即可
if (layoutState.mScrapList == null) {
....
addView(view, 0);
....
} else {
...
//否则 需要记录消失的动画信息
//再下一篇将进行分析
addDisappearingView(view);
...
}
//测量itemView的大小
measureChildWithMargins(view, 0, 0);
//每个item消耗的大小(横向或者纵向)
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
//重新计算带有ItemDecoration的大小
layoutDecoratedWithMargins(view, left, top, right, bottom);
//如果当前的Item是被删除和更改的,则不消耗空间
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
接下来咱们分析RecyclerView的onLayout流程
@Override
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() {
//宽高都精确,执行该条件
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 {
//将测量规格设置为“EXACTLY”
mLayout.setExactMeasureSpecsFrom(this);
}
//触发必要的动画和必要的资源回收
dispatchLayoutStep3();
}
private void dispatchLayoutStep3() {
//当前动画执行阶段
mState.assertLayoutStep(State.STEP_ANIMATIONS);
...
//重置阶段的状态
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
//下一篇介绍动画相关内容
...
}
//如何确保回收地 不重复 不漏掉
mLayout.removeAndRecycleScrapInt(mRecycler);
...
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
//布局完成,重置LayoutManager状态
mLayout.onLayoutCompleted(mState);
...
//恢复焦点信息
recoverFocusFromState();
resetFocusInfo();
}
ReyclerView中的复用机制是通过其内部类Recycler来实现
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
}
共有四级缓存:
接下来看一下在LinearLayoutManager中,是如何调用Recycler复用ViewHolder的。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用填充一个 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler)
-> RecyclerView.Recycler.getViewForPosition(int)
-> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView
-> Recycler.tryGetViewHolderForPositionByDeadline(...)//真正获取ViewHolder的地方
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
if (mState.isPreLayout()) {
// 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// 1) 分别从 mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder
// 这个 mHiddenViews 是用来做动画期间的复用
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) 如果 Adapter 的 hasStableIds 方法返回为 true
// 优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not know it.
// 3) 从自定义缓存获取,别问,问就是别用
View view = mViewCacheExtension
getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
if (holder == null) {
// 4) 从 RecycledViewPool 获取 ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
// 缓存全取过了,没有,那只好 create 一个咯
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
if (mState.isPreLayout() && holder.isBound()) {
...
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
//如果有必要,进行数据绑定操作
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
接下来,我们看一下,四级缓存之间是如何进行协调工作的。
// 调用链
LinearLayoutManager.onLayoutChildren(...)
-> LayoutManager.detachAndScrapAttachedViews(recycler)
-> LayoutManager.scrapOrRecycleView(..., view)
-> Recycler.scrapView(view);
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 后面会讲,缓存到 mCacheViews 和 RecyclerViewPool
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 缓存到 scrap
recycler.scrapView(view);
}
}
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
mAttachedScrap.add(holder);
} else {
mChangedScrap.add(holder);
}
}
其实还有一种情况会调用到 scrapView , 从 mHiddenViews 获得一个 ViewHolder 的话(发生在支持动画的操作),会先将这个 ViewHolder 从 mHiddenViews 数组里面移除,然后调用:
Recycler.tryGetViewHolderForPositionByDeadline(...)
-> Recycler.getScrapOrHiddenOrCachedHolderForPosition(...)
-> Recycler.scrapView(view)
void recycleViewHolderInternal(ViewHolder holder) {
if (forceRecycle || holder.isRecyclable()) {
if(mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 1. mCacheViews 满了,最早加入的不要了放 RecyclerViewPool
recycleCachedViewAt(0);
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 2. 不能放进 mCacheViews 的放 RecyclerViewPool
addViewHolderToRecycledViewPool(holder, true);
}
}
}
// Recycles a cached view and removes the view from the list
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
在这我们知道 recycleViewHolderInternal 会把 ViewHolder 缓存到 mCacheViews ,而不满足加到 mCacheViews 的会缓存到 RecycledViewPool 。那又是什么时候调用的 recycleViewHolderInternal 呢?有以下三种情况:
到这里,RecyclerView 的缓存复用机制应该分析完成了,总结一下:
参考 RecyclerView 的缓存复用机制
上一篇RecyclerView源码分析一之简单介绍
下一篇RecyclerView源码分析三之动画分析