RecyclerView.Adapter.notifyItemChanged() 源码分析

项目开发过程中遇到一个RecyclerView的问题,场景是一个页面使用RecyclerView展示本地的图片,然后用户点击其中的图片时可以改变当前图片的选中状态。我使用的是 mAdapter.notifyItemChanged() 这个方法来更新UI, 但是自测时确发现,调用这个方法之后,对应位置的Item闪了一下,可以看下面的Gif图效果。

RecyclerView.notifyItemChanged

不明白这个闪一下的动画怎么出现的(其实在测试看来是一个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);
        }
        ...
    }
}
  1. Adapter.notifyItemChanged() 方法,最终会调用到 AdapterDataObservable.notifyItemRangeChanged()方法,AdapterDataObservable是被观察者,内部维持一个观察者列表
  2. 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;
    }
}
  1. Adapter 更新的操作抽象成一个 UpdateOp 对象,这个对象保存了对应的指令: UpdateOp.UPDATE, 还有更新的位置
  2. 更新的实际操作是 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();
    }
}
  1. 在上面分析消费 UpdateOp 时知道,mState.mRunSimpleAnimations = true. if 代码块里面的逻辑主要是填充 mViewInfoStore 里面的mLayoutHolderMap,会把当前子View 的 ViewHolder 和 animationInfo 保存起来。还有会把需要更新的 ViewHolder 保存一份到 mViewInfoStore 的 mOldChangedHolders 里面。
  2. 上面的分析得到结论,需要更新的ViewHolder会在mViewInfoStore里面保存两份
  3. 调用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);
    }
}
  1. detachViewAt(index) 会把当前index的子view.parent = null, 然后RecyclerView对应的childrens数组中也会移除对它的指向
  2. 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);
    }
}
  1. 消耗 UpdateOp 之后,待更新的ViewHolder.isUpdated() = true, 其他的都为 false. 所以不需要更新的ViewHolder都会存到 mAttachedScrap 里面,而待更新的则要保存到 mChangedScrap 里面。
  2. 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;
}
  1. remainingSpace的值是通过 layoutState.mAvailable 和 layoutState.mExtraFillSpace 计算得到的,mAvailable 值通过 mOrientationHelper.getEndAfterPadding() 计算获得,在LinearLayoutManager竖直方向的情况下,大约为屏幕高度。
  2. 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;
}
  1. tryGetViewHolderForPositionByDeadline() 方法是Recycler获取ViewHolder的主要方法,缓存的逻辑就是对数据的存和取的逻辑。在这个方法里面主要涉及以下几个方面。
    • mState.isPreLayout() = true 的情况下可以从 mChangeScraps 里面取
    • 如果上面没有获取到则通过 getScrapOrHiddenOrCachedHolderForPosition 方法,这个方法里面会先检查 mAttachedScrap 在检查被隐藏的View,最后是 mCachedViews
    • 还没有获取到则通过 mViewCacheExtension 这是个抽象类的对象,需要用户自己定义通过这个对象获取缓存的方法,一般为null
    • 再有就是通过 RecycledViewPool 获取ViewHolder, 内部实现不再展开
    • 如果还没有获取到则创建一个新的ViewHolder
  2. 从 mChangeScraps/mAttachedScrap和mCachedViews 中获取的ViewHolder 不需要执行 tryBindViewHolderByDeadline 方法,新创建的ViewHolder和 RecycledViewPool 中获取的则需要
  3. 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。代码就不贴了上面有,主要对比下差异的地方。

  1. 在实际布局前还是会执行 detachAndScrapAttachedViews 方法,这个方法会把 RecyclerView 中的ViewHolder 都缓存起来,未 update 的ViewHolder 保存在 mAttachedScrap中,update 的 ViewHolder 则保存到 mChangedScrap 中。
  2. fill 方法中会循环的从 Recycler 中取ViewHolder并把对应的View attach 到RecyclerView中。
  3. 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();
    }
    ...
}
  1. mState.mRunSimpleAnimations = true, 这个可以在dispatchLayoutStep1()方法中确定。
  2. dispatchLayoutStep1中消费完UpdateOp后ViewHolde会设置 Flags = FLAG_UPDATE, 这样 viewHolder.isUpdate() = true, 然后就会被保存到 mViewInfoStore.mOldChangedHolders 中
  3. 在 dispatchLayoutStep2 中知道,当前 position 下会新建一个 ViewHolder 所以 oldChangeViewHolder == holder 是 false
  4. 在 dispatchLayoutStep1 中可知 mViewInfoStore.popFromPreLayout(oldChangeViewHolder) 的返回值不为空
  5. 动画逻辑判断完之后会执行 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: 总结
  1. 通过分析 notifyItemChanged() 的源码执行流程,知道 RecyclerView 的绘制主要在 dispatchLayout 中实现
  2. RecyclerView的 mAttachedScrap 用于缓存需要展示的 ViewHolder, 这里面的ViewHolder会经过 detach 和 attach 流程。
  3. dispatchLayoutStep1 用于预加载,dispatchLayoutStep2 是实际加载过程,dispatchLayoutStep3 处理动画相关流程。
  4. notifyItemChanged(int position, @Nullable Object payload) 这个方法用于部分更新,只会重新调用 onBindViewHolder 方法;notifyItemChanged(int position)则是全局更新,会创建新的 ViewHolder.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值