RecyclerView的onLayout浅析(一)

来源:互联网 发布:顶尖数据恢复软件 编辑:程序博客网 时间:2024/06/06 17:24

首先要感谢几位大神的分析
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,不是本文的重点

暂时先这样,下一篇继续分析

原创粉丝点击