首先要感谢几位大神的分析 RecyclerView剖析 深入浅出 RecyclerView 掌握自定义LayoutManager(二) 实现流式布局 谈谈RecyclerView的LayoutManager
(建议结合源码观看) onLayout的主要部分就是3个方法: dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3
先看第一个,进入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同样操作
普通情况下,mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations都是true
回到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里。
接下来还要判断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也是合适的。 这样就可以兼顾了
这个主要就是调用LayoutManager的onLayoutChildren,不是本文的重点
暂时先这样,下一篇继续分析