首先要感谢几位大神的分析
RecyclerView剖析
深入浅出 RecyclerView
掌握自定义LayoutManager(二) 实现流式布局
谈谈RecyclerView的LayoutManager
(建议结合源码观看)
onLayout的主要部分就是3个方法:
dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3
dispatchLayoutStep1
processAdapterUpdates
先看第一个,进入processAdapterUpdatesAndSetAnimationFlags方法
(看名字可知有2个功能:processUpdate和setFlags)
第一个判断不执行
第二个判断成立,进去里面看看
进入了AdapterHelper的preProcess方法,(当adapter的notifyItemXXX调用时,信息最后保存在这个类的成员变量mPendingUpdates)
void preProcess() {
mOpReorderer.reorderOps(mPendingUpdates);
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
applyAdd(op);
break;
case UpdateOp.REMOVE:
applyRemove(op);
break;
case UpdateOp.UPDATE:
applyUpdate(op);
break;
case UpdateOp.MOVE:
applyMove(op);
break;
}
if (mOnItemProcessedCallback != null) {
mOnItemProcessedCallback.run();
}
}
mPendingUpdates.clear();
}
第一句mOpReorderer.reorderOps(mPendingUpdates),这句会把moveOp移动到mPendingUpdates最后面,并且修正引起的偏差。
然后就是遍历所有update,以add为例,op加入到mPostponedList中,最后调用到了RecyclerView的offsetPositionRecordsForInsert(int positionStart, int itemCount)方法;
每个viewHolder有一个mPosition变量,这个方法会找出所有mPosition大于positionStart的子viewHolder,这些viewHolder的mPosition应该加上itemCount才正确,这个方法还会把layoutParams的mInsetsDirty置false,表示decoration受到影响。
还会调整Recycler的mCachedViews,调整方法与上面相同。
总结一下:
add(positionStart, itemCount):
mItemsAddedOrRemoved = true, mState.mStructureChanged = true
子view的mPosition>=positionStart的,要加上itemCount。第一次操作的话保存旧位置到mOldPosition,mPreLayoutPosition
Recycler的mCachedViews同样操作
remove(positionStart, itemCount):
mItemsAddedOrRemoved = true; mState.mStructureChanged = true;
子view在删除队列后面的,要减去itemCount。第一次操作保留旧位置
子view在删除队列里面的, 加remove的flag,mPosition变为positionStart-1,第一次操作的话保留旧位置
Recycler的mCachedViews,删除队列后面的要减去itemCount;删除队列里面的,加remove flag,扔到RecycledViewPool
update(int positionStart, int itemCount, Object payload):
mItemsChanged = true
子view在更新队列里的,加update flag。payload如为null,加fullupdate flag; 不为null,还要当没有fullupdate flag时,才加进payload list
Recycler的mCachedViews,在更新队列里的,加上update flag,扔到RecycledViewPool (进入RecycledViewPool意味着viewHolder会重新完全绑定数据)
move(int from, int to) :
mState.mStructureChanged = true; mItemsAddedOrRemoved = true
子view在from的,改变position。在后面的,position-1
Recycler的mCachedViews同样操作
setFlags
普通情况下,mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations都是true
保存变化之前子View位置
回到dispatchLayoutStep1()方法
下面来到一个判断mState.mRunSimpleAnimations,通过前面的分析,可以知道一般都为true
里面是遍历子view,看到recordPreLayoutInformation这个方法,最终调用的是这个
public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,
@AdapterChanges int flags) {
final View view = holder.itemView;
this.left = view.getLeft();
this.top = view.getTop();
this.right = view.getRight();
this.bottom = view.getBottom();
return this;
}
设置ItemHolderInfo对象的属性,然后执行这个
mViewInfoStore.addToPreLayout(holder, animationInfo);
//animationInfo就是ItemHolderInfo对象
void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
可以看到,record会放进map里面,ItemHolderInfo会记录在preInfo里。
preLayout
接下来还要判断mState.mRunPredictiveAnimations,这个一般也是true
里面执行了mLayout.onLayoutChildren(mRecycler, mState);
mLayout就是LayoutManager
现在我们要转入LayoutManager的onLayoutChildren中,去看detachAndScrapAttachedViews(recycler)这个方法
这个方法就是对所有view执行下面这个方法
//这里在RecyclerView.Recycler类中
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool.");
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
从判断可以看出,没有变化和被remove的viewHolder会放到mAttachedScrap中
而update的viewHolder,还要看canReuseUpdatedViewHolder(holder)的脸色
对于DefaultItemAnimator,如果有payload,canReuseUpdatedViewHolder返回true,也就是加到mAttachedScrap中。 没有payload,返回false,加到mChangedScrap中。
放进Recycler了,肯定还要再拿出来
//简化版
View getViewForPosition(int position, boolean dryRun) {
..........
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
.........
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
..........
}
.........
.........
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
mAdapter.bindViewHolder(holder, offsetPosition);
}
..........
}
首先第一个判断,如果在preLayout阶段,会去mChangedScrap找
还记得前面说的,没有payload的viewHolder放在mChangedScrap中,也就是说在正式layout阶段,这种viewHolder只能在RecycledViewPool找
ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
从这个判断看出,在PreLayout,viewHolder原样返回;
在正式layout时,被removed的viewHolder不会返回
看到这里,我们可以总结
在preLayout,摆放的是变化之前的位置,被remove的view经过preLayout还会存在
在正式layout时,摆放的是变化之后的位置,被remove的view经过Layout后不存在
现在来看update
看getViewForPosition的最后面,在PreLayout时,并不会重新bind
而在正式layout,如果viewHolder需要update,那就会重新bind
这个时候,payload终于派上用场了,看viewHolder的一个方法
//在adapter的bindViewHolder时调用此方法
//mUnmodifiedPayloads和mPayloads可以看成同一个东西
List<Object> getUnmodifiedPayloads() {
if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
if (mPayloads == null || mPayloads.size() == 0) {
// Initial state, no update being called.
return FULLUPDATE_PAYLOADS;
}
// there are none-null payloads
return mUnmodifiedPayloads;
} else {
// a full update has been called.
return FULLUPDATE_PAYLOADS;
}
}
还记得前面说过,调用notifyItemChange方法不带payload时,就会有FLAG_ADAPTER_FULLUPDATE这个flag
如果在子view中能找到,就会把payload放在mPayloads这个list中,所以如果payloads不为空,说明这个view已经显示在RecyclerView中,肯定已经完全绑定数据了
回到dispatchLayoutStep1
完成preLayout之后,还有一个循环,就是把preLayout中新出现的viewHolder的位置记录一下
最后clearOldPositions
//ViewHolder中
void clearOldPosition() {
mOldPosition = NO_POSITION;
mPreLayoutPosition = NO_POSITION;
}
这个时候,旧位置全部清除
讲到这个,还有个细节不知道你们有没注意到
在Recycler的getViewForPosition中,位置用的是holder.getLayoutPosition
想一想,preLayout阶段应该摆放变化之前的位置,所以用layoutPosition是合适的
现在清除了旧位置,getLayoutPosition获得的就是adapterPosition,接下来正式layout也是合适的。
这样就可以兼顾了
dispatchLayoutStep2
这个主要就是调用LayoutManager的onLayoutChildren,不是本文的重点
暂时先这样,下一篇继续分析