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,不是本文的重点
暂时先这样,下一篇继续分析
- RecyclerView的onLayout浅析(一)
- RecyclerView的onLayout浅析(二)
- RecyclerView 的源码浅析
- RecyclerView浅析
- RecyclerView浅析
- RecyclerView的使用(一)
- RecyclerView 之Adapter的简化过程浅析
- RecyclerView(一)最简单的recyclerview
- 理解RecyclerView的RecyclerView.ItemDecoration(一)
- RecyclerView与ListView对比浅析(一):初始化篇
- recyclerview的基本用法(一)
- RecyclerView 的使用详解(一)
- RecyclerView 的使用详解(一)
- RecyclerView的使用(一)
- RecyclerView的使用(一)
- Android 实现滑动的几种方法(一)onLayout方法 和 offsetLeftAndRight()与offsetTopAndBottom();
- UI控件RecyclerView浅析
- Recyclerview用法浅析
- 【英语】--闲散的时光
- JDBC的封装
- qt *.pro 和源代码通用宏定义(如条件编译)
- web第五天js
- JavaScript基本类型和引用类型的不同
- RecyclerView的onLayout浅析(一)
- LSTM 两个激励函数区别sigmoid 和tanh
- 编程实现一个单链表的测长
- 对公钥和私钥使用的理解
- MySQL触发器初探
- 问题 C: 数字排序问题
- C语言的关键字
- 火狐浏览器使用方法 使用扩展Stylish 实现浏览博客园的博文时,文字的背景颜色变为护眼绿
- C语言 头指针链表