之前学习ListView是为了更好地学习RecyclerView,没想到拖了这么久。
RecyclerView比ListView更加地复杂,主要从两个方面学习:
- RecyclerView 绘制流程
- RecyclerView 缓存复用
- ListView和RecyclerView缓存比较
RecyclerView 涉及的类
- LayoutManager : 负责子View的measure及layout
- Adapter: 适配器、负责创建子View和绑定数据到子View中
- Recycler:ViewHolder的回收和复用,有四种类型的缓存:crapped、cached、exCached、recycled
[1.0] RecyclerView 绘制流程
[1.1] onMeasure 测量
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
// LayoutManager不存在的情况
// 默认测量,见小节1.1.1
defaultOnMeasure(widthSpec, heightSpec);
return;
}
// 需要自动测量
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
// 如果是宽高都是固定数值,skipMeasure为true,跳过测量步骤
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
// 跳过测量步骤
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
// 收集ItemView的ViewHolder的信息并保存,见小节1.1.2
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
// 实际测量
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// 如果recyclerview 有额外的宽高,需要二次测量
if (mLayout.shouldMeasureTwice()) {
// 真正测量,见小节1.1.3
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
//若自定义LayoutManager,自行实现
}
}
[1.1.1] defaultOnMeasure
当不存在LayoutManager时候,会继续默认测量
void defaultOnMeasure(int widthSpec, int heightSpec) {
// 根据measureSpec算出一个具体数值
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
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);
}
}
默认测量,直接是利用View.MeasureSpec测算,所以可能会出现各种问题
[1.1.2] dispatchLayoutStep1
测量的状态有几个:
- STEP_START: 初始状态
- STEP_LAYOUT: 第一阶段preLayout完成,等待第二阶段真正的布局layout
- STEP_ANIMATIONS: 第二阶段layout完毕,等待第三阶段开始处理动画
dispatchLayoutStep1 主要工作:
- 处理adapter的数据更新
- 决定需要运行的动画
- 保存当前views的信息
- 若需要,提前layout和保存信息
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
processAdapterUpdatesAndSetAnimationFlags();
if (mState.mRunSimpleAnimations) {
// Step:保存未删除的ViewHolder的信息,进行pre_layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
...
// 保存所有的ViewHolder信息
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// 保存变化的ViewHolder
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: 运行预布局
mLayout.onLayoutChildren(mRecycler, mState);
} else {
clearOldPositions();
}
mState.mLayoutStep = State.STEP_LAYOUT;
}
[1.1.3] dispatchLayoutStep2
真正执行LayoutManager绘制的地方
private void dispatchLayoutStep2() {
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
}
最后真正的测量动作是mLayout,mLayout是LayoutManager,我们看LinearLayoutManager
// LinearLayoutManager
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// detach所有view到recycler
detachAndScrapAttachedViews(recycler);
// 倒着绘制
if (mAnchorInfo.mLayoutFromEnd) {
// 先从锚点往上
updateLayoutStateToFillStart(mAnchorInfo);
// 填充,见小节[2.1]
fill(recycler, mLayoutState, state, false);
// 再从锚点往下
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
} else {
// 正向绘制
// 先从锚点往下
updateLayoutStateToFillEnd(mAnchorInfo);
// 填充
fill(recycler, mLayoutState, state, false);
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
}
参考: https://www.jianshu.com/p/e9752f8890c8
[1.2] onLayout 布局
RecyclerView继承自ViewGroup
// RecyclerView
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
mFirstLayoutComplete = true;
}
void dispatchLayout() {
mState.mIsMeasuring = false;
// 如果未测量过,则调用dispatchLayoutStep1、dispatchLayoutStep2
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
// 如果adapter发生更新或者宽高变了,则调用dispatchLayoutStep2
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
dispatchLayout做了一次保证,保证执行dispatchLayoutStep1()、dispatchLayoutStep2(),保证测量动作已经完成,最后执行dispatchLayoutStep3(),它主要是负责执行动画
[1.2.1] dispatchLayoutStep3
dispatchLayoutStep3
// 保存信息,执行动画、相应的清理工作
private void dispatchLayoutStep3() {
this.mState.assertLayoutStep(State.STEP_ANIMATIONS);
this.mState.mLayoutStep = State.STEP_START;
if (this.mState.mRunSimpleAnimations) {
for (int i = this.mChildHelper.getChildCount() - 1; i >= 0; --i) {
// 保存viewholder布局
this.mViewInfoStore.addToPostLayout(holder, animationInfo);
}
// 执行各个执行动画
this.mViewInfoStore.process(this.mViewInfoProcessCallback);
}
// 清空view信息
this.mViewInfoStore.clear();
}
measure、layout流程
- dispatchLayoutStep1: Adapter的更新; 决定该启动哪种动画; 保存当前View的信息,如果有必要,先跑一次布局并将信息保存下来。
- dispatchLayoutStep2: 实际测量流程
- dispatchLayoutStep3: 保存View的相关信息; 触发动画; 相应的清理工作。
- LayoutManager负责真正测量子View的布局,ItemAnimator负责执行子View的动画
[1.3] onDraw 绘制
绘制基本交给了ViewGroup,RecyclerView只是绘制了ItemDecoration
public void onDraw(Canvas c) {
super.onDraw(c);
int count = this.mItemDecorations.size();
for (int i = 0; i < count; ++i) {
((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState);
}
}
[2.0] RecyclerView 缓存复用
RecyclerView 有四级缓存类型,比ListView多了两种;
缓存级别 | 参与对象 | 作用 |
---|---|---|
一级缓存 | mChangedScrap、mAttachedScrap | mChangedScrap 仅参与预布局 preLayout ;mAttachedScrap存放还会被复用的ViewHolder,但最多包含屏幕可见表项,不需要重新绑定数据 |
二级缓存 | mCachedViews | 最多存放2个缓存ViewHolder |
三级缓存 | mViewCacheExtension | 自定义实现 |
四级缓存 | mRecyclerPool | SparseArray类型,每个viewType最多可以存放5个ViewHolder |
其中,mChangedScrap实际并未参加真实的缓存过程,它的添加和移除ViewHolder都出现在dispatchLayoutStep1方法中的PreLayout(预布局)过程
[2.1] 获取缓存
fill 布局填充
// LinearLayoutManager
int fill(Recycler recycler, LinearLayoutManager.LayoutState layoutState, State state, boolean stopOnFocusable) {
int start = layoutState.mAvailable;
// 剩余空间
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LinearLayoutManager.LayoutChunkResult layoutChunkResult = this.mLayoutChunkResult;
while((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
// 根据剩余位置,不断填充空白块
this.layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
if (!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
remainingSpace -= layoutChunkResult.mConsumed;
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
return start - layoutState.mAvailable;
}
[2.1.0] layoutChunk
实际填充mCurrentPostion的位置、并添加布局子View
void layoutChunk(Recycler recycler, State state, LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.LayoutChunkResult result) {
// 获取或创建view
View view = layoutState.next(recycler);
// 添加view
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection= LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
// 测量view
...
this.measureChildWithMargins(view, 0, 0);
...
}
获取子View,会优先取出在屏幕中活跃的子View,再根据位置从废弃的子view中选出
View next(Recycler recycler) {
if (this.mScrapList != null) {
// 从Scrap优先取出活跃的view,见小节[2.1.1]
return this.nextViewFromScrapList();
} else {
// 从Recycler获取,见小节[2.1.2]
View view = recycler.getViewForPosition(this.mCurrentPosition);
this.mCurrentPosition += this.mItemDirection;
return view;
}
}
[2.1.1] nextViewFromScrapList
// 从Scrap取出缓存
private View nextViewFromScrapList() {
int size = this.mScrapList.size();
for(int i = 0; i < size; ++i) {
View view = ((ViewHolder)this.mScrapList.get(i)).itemView;
LayoutParams lp = (LayoutParams)view.getLayoutParams();
if (!lp.isItemRemoved() && this.mCurrentPosition == lp.getViewLayoutPosition()) {
this.assignPositionFromScrapList(view);
return view;
}
}
return null;
}
[2.1.2] getViewForPosition
getViewForPosition: 获取指定位置的子View,是最核心的取ViewHolder缓存或创建ViewHolder的部分
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0. 通过position从 mChangedScrap 中获取
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
//1. 通过position 从 mAttachedScrap 获取
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
//2. 通过id在mAttachedScrap集合中查找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
//3. 从自定义缓存中获取
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
}
//4.从缓存池获取
if (holder == null) {
// fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
}
//没有命中所有缓存,创建ViewHolder
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
if (mState.isPreLayout() && holder.isBound()) {
...
else {
//没有绑定就重新绑定
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
return holder;
}
实际上,mChangedScrap没有参加ViewHolder的缓存过程,只是在dispatchLayoutStep1方法中的PreLayout中缓存了一下ViewHolder;mAttachedScrap表示未发生改变的ViewHolder,不需要重新绑定,在重绘过程中,会将屏幕内的子View添加到mAttachedScrap,fill过程再从mAttachedScrap取出,不需要重绑。
[2.2] 缓存回收
当view被滑出屏幕的时候,会触发回收流程
[2.2.1] recycleView
public void recycleView(View view) {
ViewHolder holder = getChildViewHolderInt(view);
recycleViewHolderInternal(holder);
}
void recycleViewHolderInternal(ViewHolder holder) {
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
// 最大为两个
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 缓存数量大于2的时候将最先进来的ViewHolder移除
recycleCachedViewAt(0);
cachedViewSize--;
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
// 添加到recyclePool
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
mViewInfoStore.removeViewHolder(holder);
}
mCachedViews最多为两个,超过两个时候,会将最先的移除到RecyclerPool,再保存到mCachedViews。
// 回收指定位置的mCachedViews
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
[2.2.2] addViewHolderToRecycledViewPool
addViewHolderToRecycledViewPool 添加ViewHolder到mRecyclerPool
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}
RecycledViewPool的SparseArray以ViewType为区分,每种类型保存至多5个ViewHolder
//RecycledViewPool
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP; // 5
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
}
[3.0] ListView 和 RecyclerView 缓存比较
-
ListView 缓存的是子View、RecyclerView 缓存的是ViewHolder
-
ListView 的RecyclerBin 分成ActiveView和ScrapView两种;ActiveView负责回收当前界面的活跃的View,可以直接复用不需要重新绑定,用于屏幕内快速复用;ScrapView负责根据ViewType回收划出屏幕非活跃的子View,需要重新绑定
-
RecyclerView的Recycler 分成四级缓存crapped、cached、exCached、recycled,除了crapped,用于屏幕内快速复用,其他都需要重新绑定,比ListView多了cached、exCached两级
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/19389.html