自从 RecyclerView 出现,很多开发者都替换 ListView/GridView,改用它了。
首先我们要知道 Recycler 是 RecyclerView 的一个内部类,里面包括了生成新View,复用旧View,回收View,重新绑定View等逻辑。我理解 Recycler 的复用回收机制,有4级缓存:
1,mChangedScrap,当 preLayout 的时候,会从它这获取。
2,mAttachedScrap,mCachedViews,这两个也就是很多地方所谓的 ScrapView。
3,mViewCacheExtension,外部可以自定义的,但是也需要自己控制回收 View。
4,mRecyclerPool,可以外部全局共享一个 RecyclerPool。
RecyclerView 的滑动、动画或者是 NotifyDataXXX,主要流程 dispatchLayout() -> mLayout.onLayoutChildren()。
主要逻辑实际在 LayoutManager,RecyclerView 只是有一套自己的回收复用的机制,对外提供接口,而 LayoutManager 才是操作填充整个列表的,所以才有了 LinearLayoutManager,GridLayoutManager之类的。
LinearLayoutManager 类的 onLayoutChildren 方法:
@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.
// create layout state
// ......省略代码
//重绘前,重置下 ViewHolder的 falg
processAdapterUpdatesAndSetAnimationFlags();
// ......省略代码
// 把 ChildView detach,或者remove,放到 缓存
detachAndScrapAttachedViews(recycler);
// ......省略代码
// 填满整个列表,拿到 ViewHolder,绑定数据,放到RecyclerView里面
fill(recycler, mLayoutState, state, false);
}
private void processAdapterUpdatesAndSetAnimationFlags() {
// setAdapter, notifyDataXXX的时候, mDataSetHasChangedAfterLayout = true;
if (mDataSetHasChangedAfterLayout) {
//.....省略代码
//这里会把 所有ChildView的ViewHolder的flag全部add FLAG_INVALID
markKnownViewsInvalid();
//.....省略代码
}
//.....省略代码
}
//实际就是遍历所有ChildView,放到缓存
public void detachAndScrapAttachedViews(Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
return;
}
//这里的判断决定了,是 detachView (detach,会被放到 mAttachedScrap,mChangedScrap)
//还是 removeView(remove,会被放到 mCachedViews,mRecyclerPool)
// 如果 setAdapter, notifyDataXXX 的时候,ViewHolder 会是 needsUpdate | isInvalid;
// notifyDataRemove的时候,会是 isRemoved;
// ItemAnimation 有动画的时候,会是 isChanged;
// mAdapter.hasStableIds(),就表示不存在View绑定内容无效的可能;
if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() &&
!mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
}
}
如果是removeView,会调用 RecyclerView 类的 recycleViewHolderInternal 方法做缓存:
void recycleViewHolderInternal(ViewHolder holder) {
//.....省略代码
boolean cached = false;
boolean recycled = false;
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED |
ViewHolder.FLAG_CHANGED | ViewHolder.FLAG_UPDATE)) {
// mCachedViews 本身有容量限制,不可能无限制缓存View
// 如果缓存 ViewHolder 时发现超过了 mCachedView 的限制,会将最老的 ViewHolder 移到 RecycledViewPool
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
// mCachedView 缓存失败, 会移到 RecycledViewPool
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
//.....省略代码
}
上面 recycleCachedViewAt方法传的是0,所以是将最老的 ViewHolder 移到 RecycledViewPool。
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder);
mCachedViews.remove(cachedViewIndex);
}
如果是 detachView,会调用 RecyclerView 类的 scrapView 方法做缓存:
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
holder.setScrapContainer(this);
// 如果是动画,会放到 mChangedScrap;
if (!holder.isChanged() || !supportsChangeAnimations()) {
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
mChangedScrap.add(holder);
}
}
好了,上面就是把 ViewHolder 放到缓存里面的过程。
下面再来看怎么取缓存的ViewHolder:
LayoutManager类的 fill -> layoutChunk -> next ,再到 RecyclerView 类的 getViewForPosition方法:
View getViewForPosition(int position, boolean dryRun) {
//.....省略代码
boolean fromScrap = false;
ViewHolder holder = null;
// 如果是 preLayout,从 mChangedScrap 取
// Item动画的时候,会是 preLayout 状态
if (mState.isPreLayout()) {
//.....省略代码
holder = getChangedScrapViewForPosition(position);
//.....省略代码
}
if (holder == null) {
// 先从 mAttachedScrap 取,如果没有,再从 mCachedViews 取
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
//.....省略代码
}
//.....省略代码
if (holder == null) {
if (holder == null && mViewCacheExtension != null) {
//.....省略代码
// 从 mViewCacheExtension 取
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
}
if (holder == null) {
//.....省略代码
// 从 mRecycledViewPool 取
holder = getRecycledViewPool().getRecycledView(type);
//.....省略代码
}
//.....省略代码
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
//.....省略代码
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
//.....省略代码
//是否 bindViewHolder,绑定数据,ViewHolder的flag来判断
// 如果 setAdapter, notifyDataXXX 的时候,ViewHolder 会是 needsUpdate | isInvalid;
// 如果 滑出范围外 的时候,ViewHolder 会是 isBound;
mAdapter.bindViewHolder(holder, offsetPosition);
//.....省略代码
}
//.....省略代码
// 得到 LayoutParams,赋值 mViewHolder
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
//.....省略代码
return holder.itemView;
}