-
概述
当列表数据发生变化时,需要调用Adapter中的一些API来通知RecyclerView改变UI,比如列表刷新、添加、移除、部分数据项改变等,在这些数据更改时需要告诉RecyclerView从而做出改变,本文会根据常用数据更新场景来分析一下是怎么做到的。
在开始之前,我们要确定一个规范,那就是一定要是数据驱动UI,在onBindViewHolder中进行数据绑定的时候把UI的所有组件的状态和对应数据段对应在一起,然后当数据改变时,我们再调用相关API通知数据变化,这时会重新走onBindViewHolder方法给对应组件重新赋值,这就保证了数据源一致性,切不可在onCreateViewHolder或者onBindViewHolder中根据不同情况动态的设置组件状态,不然就会导致多个地方可以修改组建,造成难以捕捉的混乱错误。
-
notifyDataSetChanged
方法,在所有数据项替换时需要调用,比如刷新操作,当然,所有情况的数据改变你都可以通过这个方法更新UI,但是为了某几项的数据改变去更新所有项显然会影响性能,所以这才有了下面的这些方法,用于更新一部分项; -
notifyItemChanged
方法,在某一项数据改变时调用; -
notifyItemRangeChanged
方法,在连续的一部分项数据改变时调用; -
notifyItemInserted
方法,在插入某一项数据时调用; -
notifyItemMoved
方法,在移动某一项数据的位置时调用,传入一个当前位置参数和一个目标位置参数; -
notifyItemRangeInserted
方法,在插入连续的一部分项的数据时调用; -
notifyItemRemoved
方法,在移除某一项数据时调用; -
notifyItemRangeRemoved
方法,在移除连续的一部分项数据时调用。
上述这些方法内部都是调用了mObservable的同名方法,mObservable是什么呢?它在创建Adapter实例时自动创建:
private final AdapterDataObservable mObservable = new AdapterDataObservable();
AdapterDataObservable继承自Observable,mObservers中存放了多个AdapterDataObserver,上述的每个方法都会调用下面的循环代码去调用每一个AdapterDataObserver的同名方法:
for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onXxxx(...); }
mObservers是通过Observable的registerObserver方法注册的,这个方法在Adapter的registerAdapterDataObserver中调用,在setAdapter流程中会调用这个方法注册:
adapter.registerAdapterDataObserver(mObserver);
mObserver是在RecyclerView实例化的时候自动创建的:
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
在RecyclerViewDataObserver中,和AdapterDataObservable类似,每个方法都是调用了mAdapterHelper的同名方法:
assertNotInLayoutOrScroll(null); if (mAdapterHelper.onXxx(...)) { triggerUpdateProcessor(); }
triggerUpdateProcessor方法就是通知View树重新layout:
void triggerUpdateProcessor() { if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; requestLayout(); } }
特殊的notifyDataSetChanged调用的是onChanged方法:
@Override public void onChanged() { assertNotInLayoutOrScroll(null); mState.mStructureChanged = true; processDataSetCompletelyChanged(true); if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } }
下面逐一分析mAdapterHelper的这些方法。
-
-
全部更新 — processDataSetCompletelyChanged
根据上面的梳理,我们知道notifyDataSetChanged方法调用最后会调用RecyclerViewDataObserver的onChange方法,这里面会调用processDataSetCompletelyChanged方法:
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { mDispatchItemsChangedEvent |= dispatchItemsChanged; mDataSetHasChangedAfterLayout = true; markKnownViewsInvalid(); }
这里调用了markKnownViewsInvalid方法:
void markKnownViewsInvalid() { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore()) { holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); } } markItemDecorInsetsDirty(); mRecycler.markKnownViewsInvalid(); }
mChildHelper.getUnfilteredChildCount得到的是所有已经attach到RecyclerView的ViewHolder,包括隐藏的,这里会给所有attach的ViewHolder设置FLAG_UPDATE和FLAG_INVALID属性,markItemDecorInsetsDirty方法会把这些ViewHolder的itemView的decorInset(即padding这些)设置为dirty,markKnownViewsInvalid会清除mCacheViews并且把它里面的ViewHolder放到mScrapHeap中去。
待到requestLayout触发新数据布局的时候走到tryGetViewHolderForPositionByDeadline方法中,因为mCacheViews中清空了,所以此时只有getRecycledViewPool().getRecycledView(type)取到的mScrapHeap中有之前缓存的ViewHolder,还记得它的默认大小是5吗,所以滚动显示前5个表项的时候是不会走onCreateViewHolder的,注意这里的5个会包括可以在屏幕中显示的,因为重新设置新的数据内容,相当于所有的表项都会销毁,所以当前在屏幕中的也会被缓存,而滚动的时候则只有在超出屏幕某个范围之后才会缓存。
不管是不是重新构造的ViewHolder,都会走onBindViewHolder方法,因为之前markKnownViewsInvalid中把当时所有attach的View都会添加FLAG_UPDATE和FLAG_INVALID标志,而在tryGetViewHolderForPositionByDeadline中会根据这些标志调用onBindViewHolder:
if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }
-
部分更新
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { if (itemCount < 1) { return false; } mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); mExistingUpdateTypes |= UpdateOp.UPDATE; return mPendingUpdates.size() == 1; }
Adapter中的notifyItemChanged、notifyItemRangeChanged其实都是走的这个方法,只不过notifyItemChanged传入的itemCount固定为1。
obtainUpdateOp方法如下:
@Override public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) { UpdateOp op = mUpdateOpPool.acquire(); if (op == null) { op = new UpdateOp(cmd, positionStart, itemCount, payload); } else { op.cmd = cmd; op.positionStart = positionStart; op.itemCount = itemCount; op.payload = payload; } return op; }
现在我们知道了onItemRangeChanged其实就是在mPendingUpdates中添加一个UpdateOp。
其他的onItemRangeInserted、onItemRangeRemoved和onItemRangeMoved都是调用obtainUpdateOp在mPendingUpdates中添加一个UpdateOp,但是UpdateOp的cmd属性分别设置为UpdateOp.ADD、UpdateOp.REMOVE、UpdateOp.MOVE。
然后调用requestLayout之后就会调用onLayout方法,onLayout中调用了dispatchLayout方法,部分代码如下:
if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) { // First 2 steps are done in onMeasure but looks like we have to run again due to // changed size. mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else { // always make sure we sync them (to ensure mode is exact) mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3();
在dispatchLayoutStep3中一开始会把dispatchLayoutStep3置为State.STEP_START,它的默认值也是State.STEP_START,所以在开始一次全新的layout的时候一定会是State.STEP_START的,那么就会调用dispatchLayoutStep1方法,这个方法中会调用processAdapterUpdatesAndSetAnimationFlags方法,里面有一段:
if (predictiveItemAnimationsEnabled()) { mAdapterHelper.preProcess(); } else { mAdapterHelper.consumeUpdatesInOnePass(); }
predictiveItemAnimationsEnabled会决定布局是否支持动画更新:
private boolean predictiveItemAnimationsEnabled() { return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); }
mItemAnimator会自动初始化为DefaultItemAnimator,所以一定不为null,所以这里取决于LayoutManager的supportsPredictiveItemAnimations方法返回,默认返回false,LinearLayoutManager中重写了它:
@Override public boolean supportsPredictiveItemAnimations() { return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd; }
mPendingSavedState在onRestoreInstanceState恢复中才会赋值,在onLayoutCompleted布局完成中会置为null,那这里的意思就是在界面恢复过程中不需要展示动画,可以理解,因为界面恢复工作是为了默默地恢复之前的界面,最好不要让用户感知到,加上动画是此地无银了。还有一个条件就是布局方向和上一次相比没有发生变化,比如上次是从上而下的顺序,插入一个表项之后改成从下往上填充了,那这种情况也不会加上动画。
界面恢复和临时更改布局方向都是很少出现的情况,所以默认都是会走preProcess逻辑,但是这里consumeUpdatesInOnePass的逻辑我也会列出来,我们先看preProcess。
-
mAdapterHelper.preProcess()
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(); }
这个方法中会根据不同操作调用不同方法。
-
添加操作 — applyAdd方法
private void applyAdd(UpdateOp op) { postponeAndUpdateViewHolders(op); }
这里直接调用了postponeAndUpdateViewHolders方法:
private void postponeAndUpdateViewHolders(UpdateOp op) { if (DEBUG) { Log.d(TAG, "postponing " + op); } mPostponedList.add(op); switch (op.cmd) { case UpdateOp.ADD: mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); break; case UpdateOp.MOVE: mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); break; case UpdateOp.REMOVE: mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart, op.itemCount); break; case UpdateOp.UPDATE: mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); break; default: throw new IllegalArgumentException("Unknown update op type for " + op); } }
当UpdateOp的cmd属性是UpdateOp.ADD的时候会调用Callback.offsetPositionsForAdd(op.positionStart, op.itemCount)方法,这个方法中会调用offsetPositionRecordsForInsert方法:
void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
if (DEBUG) {
Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
+ holder + " now at position " + (holder.mPosition + itemCount));
}
holder.offsetPosition(itemCount, false);
mState.mStructureChanged = true;
}
}
mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
requestLayout();
}这个方法做了两件事,一件是通过getUnfilteredChildAt方法循环获取已经attach的ViewHolder,凡是holder.mPosition大于positionStart的都是在插入元素之后的表项,offsetPosition把这些ViewHolder的mPosition属性都在原来基础上加上itemCount,从而达到在插入元素之后的表项同步位移的效果;另一件事就是调用Recycler的offsetPositionRecordsForInsert方法修改mCacheViews中的ViewHolder的mPosition属性,也就是同步操作,**注意,这里并没有同步其他的缓存集合,所以那些缓存集合中的mPosition和修改后的位置就不匹配了,这就会导致那些缓存失效**。 offsetPosition里修改mPosition代码:**mPosition += offset**。 其他的移动、删除、更新操作的原理都和这里的添加操作同理,下面就不多说了,只是由于它们的操作的目的不同,所以偏移的算法会不一样,下面只会分析它们自己的偏移算法。 + ### 移动操作 — applyMove方法 applyMove方法也是直接调用了postponeAndUpdateViewHolders方法,同理move操作会调用RecyclerView的offsetPositionRecordsForMove方法: ```java void offsetPositionRecordsForMove(int from, int to) { final int childCount = mChildHelper.getUnfilteredChildCount(); final int start, end, inBetweenOffset; //start永远是position更小的那个,而end会是更大的那个 //inBetweenOffset标志着偏移方向,上移就是减1,下移就是加1 if (from < to) { start = from; end = to; inBetweenOffset = -1; } else { start = to; end = from; inBetweenOffset = 1; } for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); //根据start和end锁定偏移范围,mPosition小于start和大于end的表项不移动 if (holder == null || holder.mPosition < start || holder.mPosition > end) { continue; } if (DEBUG) { Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " + holder); } //找到当前需要移动的目标ViewHolder了,to - from直接算出带方向的偏移距离,mPosition加上这个值就是偏移后的下标 if (holder.mPosition == from) { holder.offsetPosition(to - from, false); } else { //start和end之间的表项位移 holder.offsetPosition(inBetweenOffset, false); } mState.mStructureChanged = true; } mRecycler.offsetPositionRecordsForMove(from, to); requestLayout(); }
Recycler的offsetPositionRecordsForMove方法里面和这里的算法一样,只不过它的操作对象是mCacheView。
-
更新操作 — applyUpdate方法
applyUpdate方法里会计算出attach的ViewHolder中可见的那些的偏移位置,然后调用postponeAndUpdateViewHolders方法,最后会调用到RecyclerView的viewRangeUpdate方法:
void viewRangeUpdate(int positionStart, int itemCount, Object payload) { final int childCount = mChildHelper.getUnfilteredChildCount(); final int positionEnd = positionStart + itemCount; for (int i = 0; i < childCount; i++) { final View child = mChildHelper.getUnfilteredChildAt(i); final ViewHolder holder = getChildViewHolderInt(child); if (holder == null || holder.shouldIgnore()) { continue; } if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { // We re-bind these view holders after pre-processing is complete so that // ViewHolders have their final positions assigned. holder.addFlags(ViewHolder.FLAG_UPDATE); holder.addChangePayload(payload); // lp cannot be null since we get ViewHolder from it. ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; } } mRecycler.viewRangeUpdate(positionStart, itemCount);
}
这个方法很简单,就是给attach的ViewHolder添加FLAG_UPDATE标签。 + ### 移除操作 — applyRemove方法 这个方法里同样会计算出在要移除的范围内,attach的ViewHolder中可见的那些的偏移位置。然后调用postponeAndUpdateViewHolders方法,最后会调用到RecyclerView的offsetPositionRecordsForRemove方法: ```java void offsetPositionRecordsForRemove(int positionStart, int itemCount, boolean applyToPreLayout) { final int positionEnd = positionStart + itemCount; final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore()) { if (holder.mPosition >= positionEnd) { if (DEBUG) { Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + " holder " + holder + " now at position " + (holder.mPosition - itemCount)); } holder.offsetPosition(-itemCount, applyToPreLayout); mState.mStructureChanged = true; } else if (holder.mPosition >= positionStart) { if (DEBUG) { Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + " holder " + holder + " now REMOVED"); } holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, applyToPreLayout); mState.mStructureChanged = true; } } } mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); requestLayout(); }
flagRemovedAndOffsetPosition方法如下:
void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { addFlags(ViewHolder.FLAG_REMOVED); offsetPosition(offset, applyToPreLayout); mPosition = mNewPosition; }
注意,这里添加了FLAG_REMOVED标签,这里调用的offsetPosition方法中:
if (applyToPreLayout) { mPreLayoutPosition += offset; } mPosition += offset;
因为这里的applyToPreLayout传进来的是false,所以mPreLayoutPosition不会同步到最新位置,还是保留之前表项的存在位置,这很关键。mPosition会被置为新的移除后的位置,也就是移除的第一个表项的前一个位置。
-
-
mAdapterHelper.consumeUpdatesInOnePass方法
void consumeUpdatesInOnePass() { // we still consume postponed updates (if there is) in case there was a pre-process call // w/o a matching consumePostponedUpdates. consumePostponedUpdates(); final int count = mPendingUpdates.size(); for (int i = 0; i < count; i++) { UpdateOp op = mPendingUpdates.get(i); switch (op.cmd) { case UpdateOp.ADD: mCallback.onDispatchSecondPass(op); mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); break; case UpdateOp.REMOVE: mCallback.onDispatchSecondPass(op); mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount); break; case UpdateOp.UPDATE: mCallback.onDispatchSecondPass(op); mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); break; case UpdateOp.MOVE: mCallback.onDispatchSecondPass(op); mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); break; } if (mOnItemProcessedCallback != null) { mOnItemProcessedCallback.run(); } } recycleUpdateOpsAndClearList(mPendingUpdates); mExistingUpdateTypes = 0; }
如果在上述调用dispatchLayoutStep2流程中调用的这个方法,因为mPendingUpdates在preProcess中clear了,所以只会走consumePostponedUpdates方法,里面会循环mPostponedList逐个调用mCallback.onDispatchSecondPass方法,mCallback在构造AdapterHelper的时候构造,mCallback.onDispatchSecondPass方法最终调用的是它的dispatchUpdate方法:
void dispatchUpdate(AdapterHelper.UpdateOp op) { switch (op.cmd) { case AdapterHelper.UpdateOp.ADD: mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); break; case AdapterHelper.UpdateOp.REMOVE: mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); break; case AdapterHelper.UpdateOp.UPDATE: mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, op.payload); break; case AdapterHelper.UpdateOp.MOVE: mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); break; } }
发现LinearLayoutManager中并没有实现父类的这些方法,所以这里其实就是给自定义回调处理提供入口而已。
如果是在dispatchLayoutStep1中调用的这个方法,会根据UpdateOp的cmd调用不同的方法,这和上面的preProcess流程调用的mCallback的方法大部分是完全相同的,只不过remove的时候有所不同,这里remove操作最后调用的也是offsetPositionsForRemovingInvisible方法,只不过这里传入的applyToPreLayout方法是true,所以这里的mPreLayoutPosition也会同步到最新位置,不过暂时mPreLayoutPosition这个值还没发现哪里用到了。
-
刷新UI
以上在dispatchLayoutStep1中,preProcess一个主要的工作就是把我们调用Adapter.notifyDataXxx流程中保存的mPendingUpdates给保存到了mPostponedList中,mPendingUpdates的作用主要是用于在preProcess中设置FLAG,但是对我们这里的原理其实帮助不大,可以把它理解成一个摆渡者,把更新信息摆渡给mPostponedList。
在dispatchLayoutStep2流程中,我们最终会调用tryGetViewHolderForPositionByDeadline方法,在这个方法中有这么一段:
if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }
这里会通过mAdapterHelper的findPositionOffset方法计算出最新数据的位置:
int findPositionOffset(int position, int firstPostponedItem) { int count = mPostponedList.size(); for (int i = firstPostponedItem; i < count; ++i) { UpdateOp op = mPostponedList.get(i); if (op.cmd == UpdateOp.MOVE) { if (op.positionStart == position) { position = op.itemCount; } else { if (op.positionStart < position) { position--; // like a remove } if (op.itemCount <= position) { position++; // like an add } } } else if (op.positionStart <= position) { if (op.cmd == UpdateOp.REMOVE) { if (position < op.positionStart + op.itemCount) { return -1; } position -= op.itemCount; } else if (op.cmd == UpdateOp.ADD) { position += op.itemCount; } } } return position; }
然后调用tryBindViewHolderByDeadline方法回调到bindViewHolder方法完成新位置的数据更新,之后呢,就交给fill方法进行布局和偏移了。
-
总结
纵上可知,数据变化触发RecyclerView更新UI的原理如下:
Adapter中维护了一个AdapterDataObservable实例,可以通过它添加多个响应数据变化的监听器,那么我们这里在setAdapter时(setAdapterInternal中)会设置一个监听器,叫做RecyclerViewDataObserver,调用Adapter的更新数据的方法后,AdapterDataObservable会通知所有监听器数据改变,这里就会调用到RecyclerViewDataObserver的对应方法。
RecyclerViewDataObserver的相关方法中,按照逻辑途径来说有两类操作,一类是全部数据发生改变时调用的notifyDataSetChanged方法的逻辑;另一个是添加、移除、部分change或者移动这些部分更新的逻辑。前者会直接遍历所有目前已经attach的ViewHolder,调用addFlags方法添加ViewHolder.FLAG_UPDATE和ViewHolder.FLAG_INVALI标签;后者会调用AdapterHelper的相关方法来进行处理。但其实最终的最终都是修改ViewHolder的flags。
-
AdapterHelper中维护了一个ArrayList
,叫做mPendingUpdates,这里的对应方法中会构造一个新的UpdateOp或者从pool中取出一个复用,设置UpdateOp的相关属性,其中cmd表示操作类型,有ADD、REMOVE、UPDATE、MOVE四种,positionStart表示要改动的起始位置,itemCount表示从起始位置开始需要修改的表项个数,最后会把UpdateOp对象添加到mPendingUpdates中,mExistingUpdateTypes会通过或操作把下次布局时需要执行的操作类型记录,在合适的时机会调用hasAnyUpdateTypes查看是否含有需要执行的操作类型: boolean hasAnyUpdateTypes(int updateTypes) { return (mExistingUpdateTypes & updateTypes) != 0; }
其实可以通过遍历mPendingUpdates达到一样的效果,这里主要是为了性能考虑。
mPendingUpdates中保存的都是需要更新的ViewHolder,接着会调用requestLayout方法。
dispatchLayoutStep1方法中会调用processAdapterUpdatesAndSetAnimationFlags方法,这里会调用preProcess方法,这个方法首先会把这些UpdateOp保存到mPostponedList中,然后会根据mPendingUpdates中的UpdateOp修改对应的attach的ViewHolder。
notifyDataSetChanged逻辑因为直接修改的FLAG,没有操作mPendingUpdates,所以调用了preProcess也不会发生任何操作。
然后调用dispatchLayoutStep2方法进行fill布局,在tryGetViewHolderForPositionByDeadline中会从回收区中尝试拿回尚未丢弃的holder,这里其实和ViewHolder是什么样的没有关系,关键的是bindViewHolder方法的position参数是什么,这里会通过mAdapterHelper.findPositionOffset(position)方法遍历mPostponedList,找到对应的UpdateOp,根据它修改当前的position,然后传给bindViewHolder,从而达到数据更新的目的。
总之,其实中心思想就是给bindViewHolder方法的position找最新的值,最核心的关键就是先保存要修改的position信息,然后在布局的时候根据它去数据集里获取针对这个位置的数据,从而达到更新的效果。ViewHolder拿到了能用就复用,不能用就创建新的,其实preProcess里面那么繁琐的修改,主要是为了之后根据FLAG复用ViewHolder。