RecyclerView的onLayout浅析(一)

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

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

    原文作者:nh632343
    原文地址: https://blog.csdn.net/nh632343/article/details/76795444
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞