项目开发过程中遇到一个RecyclerView的问题,场景是一个页面使用RecyclerView展示本地的图片,然后用户点击其中的图片时可以改变当前图片的选中状态。我使用的是 mAdapter.notifyItemChanged() 这个方法来更新UI, 但是自测时确发现,调用这个方法之后,对应位置的Item闪了一下,可以看下面的Gif图效果。
不明白这个闪一下的动画怎么出现的(其实在测试看来是一个BUG), 所以决定研究一下。
Part one: RecyclerView.Adapter.notifyItemChanged()
代码调用
public abstract static class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
}
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount,
@Nullable Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
}
class RecyclerView {
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
...
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
...
}
}
- Adapter.notifyItemChanged() 方法,最终会调用到 AdapterDataObservable.notifyItemRangeChanged()方法,AdapterDataObservable是被观察者,内部维持一个观察者列表
- RecyclerView.setAdapter() 方法调用时,会为当前 adapter 设置一个观察者,这个观察者是一个RecyclerViewDataObserver对象。
实际的代码逻辑
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
class AdapterHelper implements OpReorderer.Callback {
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 更新的操作抽象成一个 UpdateOp 对象,这个对象保存了对应的指令: UpdateOp.UPDATE, 还有更新的位置
- 更新的实际操作是 requestLayout() 方法,这个方法是刷新当前View的方法,会重新执行整个绘制流程。具体的实现逻辑不再描述。
通过上面的代码可以知道的是,更新操作抽象为一个UpdateOp 对象,保存在 AdapterHelper 对象中,然后调用 requestLayout() 方法来重新绘制 Recycler , 这个是轻量级绘制,因为已经展示了,所以会使用之前的一些缓存,因而不会太影响性能。
Part Two: RecyclerView重绘
RecyclerView的 onMeasure() 方法只是涉及到 View 大小相关的逻辑,没有涉及到对子类的处理逻辑,所以可以从 onLayout 开始分析。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
void 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();
}
onLayout的主要逻辑通过 dispatchLayoutStep1(), dispatchLayoutStep2(), dispatchLayoutStep3() 这三个方法实现。所以也主要分析这三个方法。
RecyclerView pre-layout 状态,消费 UpdateOp
UpdateOp的消费代码
private void dispatchLayoutStep1() {
...
processAdapterUpdatesAndSetAnimationFlags();
...
}
private void processAdapterUpdatesAndSetAnimationFlags() {
...
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
这个方法做了两件事,一个是消费 Adapter 的更新事件,另一个是设置动画的Flags, mRunSimpleAnimations = mRunPredictiveAnimations = true, 这个在之后的逻辑中会用到。
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.UPDATE:
applyUpdate(op);
break;
}
...
}
mPendingUpdates.clear();
}
private void applyUpdate(UpdateOp op) {
...
if (type == POSITION_TYPE_INVISIBLE) {
dispatchAndUpdateViewHolders(op);
} else {
postponeAndUpdateViewHolders(op);
}
}
private void postponeAndUpdateViewHolders(UpdateOp op) {
...
case UpdateOp.UPDATE:
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
break;
...
}
上面的三个方法都是在 AdapterHelper 中定义的,就是从 mPendingUpdates 中取出 UpdateOp, 然后执行对应的操作,最后走到 mCallback.markViewHoldersUpdated 这个方法,这个mCallback 是在RecyclerView 中定义的。
@Override
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
viewRangeUpdate(positionStart, itemCount, payload);
mItemsChanged = true;
}
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);
}
public abstract static class ViewHolder {
void addChangePayload(Object payload) {
if (payload == null) {
addFlags(FLAG_ADAPTER_FULLUPDATE);
} else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
createPayloadsIfNeeded();
mPayloads.add(payload);
}
}
}
上面的方法会为需要更新的ViewHolder设置 flag = ViewHolder.FLAG_UPDATE. 并且,如果 payload = null 的情况下还会设置 FLAG_ADAPTER_FULLUPDATE. 否则就创建 mPayloads . 这个在之后的代码中能看到它的用法。
到这一步可以看到Recycler消费了 UpdateOp 操作,主要是通过改变 ViewHolder 的flags来实现的。RecyclerView有自己的缓存逻辑,缓存的对象就是ViewHolder, ViewHolder是RecyclerView中很重要的一个对象,接下来的分析就能看到。
RecyclerView 预布局
private void dispatchLayoutStep1() {
...
processAdapterUpdatesAndSetAnimationFlags();
...
mState.mInPreLayout = mState.mRunPredictiveAnimations;
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
} else {
clearOldPositions();
}
}
- 在上面分析消费 UpdateOp 时知道,mState.mRunSimpleAnimations = true. if 代码块里面的逻辑主要是填充 mViewInfoStore 里面的mLayoutHolderMap,会把当前子View 的 ViewHolder 和 animationInfo 保存起来。还有会把需要更新的 ViewHolder 保存一份到 mViewInfoStore 的 mOldChangedHolders 里面。
- 上面的分析得到结论,需要更新的ViewHolder会在mViewInfoStore里面保存两份
- 调用LayoutManager.onLayoutChildren()方法,给子View布局
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
detachAndScrapAttachedViews(recycler);
...
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
...
}
这个方法中的执行逻辑,会先把当前子View对应的ViewHolder存到 mAttachScraps 的缓存中,然后 fill() 方法中,再从缓存中取出填充到RecyclerView中。
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
...
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
- detachViewAt(index) 会把当前index的子view.parent = null, 然后RecyclerView对应的childrens数组中也会移除对它的指向
- recycler.scrapView(view); 会缓存 view 对应的 ViewHolder.
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." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
- 消耗 UpdateOp 之后,待更新的ViewHolder.isUpdated() = true, 其他的都为 false. 所以不需要更新的ViewHolder都会存到 mAttachedScrap 里面,而待更新的则要保存到 mChangedScrap 里面。
- canReuseUpdatedViewHolder(holder) 这个方法和payload有关,后面会分析其用法。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
return start - layoutState.mAvailable;
}
- remainingSpace的值是通过 layoutState.mAvailable 和 layoutState.mExtraFillSpace 计算得到的,mAvailable 值通过 mOrientationHelper.getEndAfterPadding() 计算获得,在LinearLayoutManager竖直方向的情况下,大约为屏幕高度。
- layoutChunk 方法会从Recycler中取ViewHolder,并把对应的View填充到RecyclerView中。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
...
}
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount()
+ exceptionLabel());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
if (holder == null && mViewCacheExtension != null) {
...
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
...
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
return holder;
}
- tryGetViewHolderForPositionByDeadline() 方法是Recycler获取ViewHolder的主要方法,缓存的逻辑就是对数据的存和取的逻辑。在这个方法里面主要涉及以下几个方面。
- mState.isPreLayout() = true 的情况下可以从 mChangeScraps 里面取
- 如果上面没有获取到则通过 getScrapOrHiddenOrCachedHolderForPosition 方法,这个方法里面会先检查 mAttachedScrap 在检查被隐藏的View,最后是 mCachedViews
- 还没有获取到则通过 mViewCacheExtension 这是个抽象类的对象,需要用户自己定义通过这个对象获取缓存的方法,一般为null
- 再有就是通过 RecycledViewPool 获取ViewHolder, 内部实现不再展开
- 如果还没有获取到则创建一个新的ViewHolder
- 从 mChangeScraps/mAttachedScrap和mCachedViews 中获取的ViewHolder 不需要执行 tryBindViewHolderByDeadline 方法,新创建的ViewHolder和 RecycledViewPool 中获取的则需要
- addView() 方法主要有两个逻辑,一个是判断这个View对应的ViewHolder是否是从 scrap 中获取到的,如果是则会先从缓存中移除;另一个逻辑是把当前View attach 到 RecyclerView 中,前面缓存ViewHolder的时候会把View detach 这里则会 attach
到这里 dispatchLayoutStep1 的主要逻辑就执行完了,这也是RecyclerView 的 pre-layout 流程。之后的 dispatchLayoutStep2 也会执行大体上相同的逻辑,不一样的地方在于 mState.mInPreLayout = false. 那么 pre-layout 的含义在于什么哪,这个需要再 dispatchLayout3的时候才能知道。
RecyclerView 实际布局
private void dispatchLayoutStep2() {
...
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
...
}
dispatchLayoutStep2中又调用了 mLayout.onLayoutChildren(mRecycler, mState) 方法,不同的时这次 mState.mInPreLayout = false。代码就不贴了上面有,主要对比下差异的地方。
- 在实际布局前还是会执行 detachAndScrapAttachedViews 方法,这个方法会把 RecyclerView 中的ViewHolder 都缓存起来,未 update 的ViewHolder 保存在 mAttachedScrap中,update 的 ViewHolder 则保存到 mChangedScrap 中。
- fill 方法中会循环的从 Recycler 中取ViewHolder并把对应的View attach 到RecyclerView中。
- tryGetViewHolderForPositionByDeadline() 是从 Recycler 中获取ViewHolder的主逻辑,在这个里面,因为这次 mState.isPreLayout() = false 所以不会从 mChangedScrap 中取对应的 ViewHolder, 那么就会导致重新创建一个 ViewHolder. 从而会走 onCreateViewHolder 和 onBinderViewHolder 方法。
dispatchLayoutStep2() 这个方法会创建一个新的 ViewHolder 用于展示需要 update 的Item. 这个时候还没有和动画相关的逻辑,所以这就是 dispatchLayoutStep3 的用途
RecyclerView 展示动画
private void dispatchLayoutStep3() {
...
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
// we add and remove so that any post info is merged.
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
}
mLayout.removeAndRecycleScrapInt(mRecycler);
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
...
}
- mState.mRunSimpleAnimations = true, 这个可以在dispatchLayoutStep1()方法中确定。
- dispatchLayoutStep1中消费完UpdateOp后ViewHolde会设置 Flags = FLAG_UPDATE, 这样 viewHolder.isUpdate() = true, 然后就会被保存到 mViewInfoStore.mOldChangedHolders 中
- 在 dispatchLayoutStep2 中知道,当前 position 下会新建一个 ViewHolder 所以 oldChangeViewHolder == holder 是 false
- 在 dispatchLayoutStep1 中可知 mViewInfoStore.popFromPreLayout(oldChangeViewHolder) 的返回值不为空
- 动画逻辑判断完之后会执行 mLayout.removeAndRecycleScrapInt(mRecycler) 这个方法会回收 mAttachedScrap 中的ViewHolder, 然后清空 mChangedScrap 里的ViewHolder.
private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
boolean oldHolderDisappearing, boolean newHolderDisappearing) {
oldHolder.setIsRecyclable(false);
...
if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
}
上面就是动画执行的主要逻辑,mItemAnimator 是一个 DefaultItemAnimator 对象,所以最终是 调用了 DefaultItemAnimator.runPendingAnimations()
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
...
if (changesPending) {
final ArrayList<ChangeInfo> changes = new ArrayList<>();
changes.addAll(mPendingChanges);
mChangesList.add(changes);
mPendingChanges.clear();
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
changes.clear();
mChangesList.remove(changes);
}
}
}
...
}
void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
final View view = holder == null ? null : holder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view != null) {
final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
getChangeDuration());
mChangeAnimations.add(changeInfo.oldHolder);
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchChangeStarting(changeInfo.oldHolder, true);
}
@Override
public void onAnimationEnd(Animator animator) {
oldViewAnim.setListener(null);
view.setAlpha(1);
view.setTranslationX(0);
view.setTranslationY(0);
dispatchChangeFinished(changeInfo.oldHolder, true);
mChangeAnimations.remove(changeInfo.oldHolder);
dispatchFinishedWhenDone();
}
}).start();
}
...
}
上面就是实际的动画,忽略了一些相关代码,不然就太长了。大体上是 dispatchLayoutStep2() 中新建的 ViewHolder 执行展示的动画,dispatchLayoutStep1 中放到 mViewInfoStore.mOldChangedHolders 中的ViewHolder 执行消失的动画。当消失的动画执行完之后,会把对应的 ViewHolder 保存到 RecycledViewPool 中,代码就不贴了。
Part Three: 解决闪一下的问题
通过上面的分析可知,如果在 dispatchLayoutStep3 中,mViewInfoStore.getFromOldChangeHolders(key) 返回空那么就不会执行动画。而在 dispatchLayoutStep1()中如果ViewHolder被放到mChangedScrap中,那么这个ViewHolder就会被放到 oldChangeHolders 中,所以需要找到不放到 mChangedScrap 中的方法。
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." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
在这段代码中消耗 UpdateOp 之后ViewHolder会被设置 FLAG_UPDATE 所以在这里 holder.isUpdate() = true. 那么要想任然放到 mAttachScrap 中就需要看看 canReuseUpdatedViewHolder(holder) 是怎么实现的。
boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
viewHolder.getUnmodifiedPayloads());
}
public class DefaultItemAnimator extends SimpleItemAnimator {
@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull List<Object> payloads) {
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}
}
如果 payloads不为空,那么 canReuseUpdatedViewHolder 就返回 true, 此时ViewHolder就会被放到 mAttachedScrap 里面。
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);
}
void addChangePayload(Object payload) {
if (payload == null) {
addFlags(FLAG_ADAPTER_FULLUPDATE);
} else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
createPayloadsIfNeeded();
mPayloads.add(payload);
}
}
上面是消费 UpdateOp 的代码,在这个方法里面会调用 ViewHolder.addChangePayload(payload) 如果payload 不为空,那么 ViewHolder.mPayloads 就不为空,而这个payload就是调用 Adapter.notifyItemChanged(int position, @Nullable Object payload) 传的 payload 参数。
Part Four: 总结
- 通过分析 notifyItemChanged() 的源码执行流程,知道 RecyclerView 的绘制主要在 dispatchLayout 中实现
- RecyclerView的 mAttachedScrap 用于缓存需要展示的 ViewHolder, 这里面的ViewHolder会经过 detach 和 attach 流程。
- dispatchLayoutStep1 用于预加载,dispatchLayoutStep2 是实际加载过程,dispatchLayoutStep3 处理动画相关流程。
- notifyItemChanged(int position, @Nullable Object payload) 这个方法用于部分更新,只会重新调用 onBindViewHolder 方法;notifyItemChanged(int position)则是全局更新,会创建新的 ViewHolder.