(4.0.22.11)Android RecyclerView 局部刷新分析

我们知道,RecyclerView 中已经添加了 notifyItemChange() notifyItemRemove() 等等单个条目更改的方法,大方向说,这个相对于 ListView 或者 notifyDataChange() 方法 , 它已经算是做到局部刷新。小方向再说,这些添加的刷新方法,其实默认都是带有动画效果,具体效果是 DefaultItemAnimator 来控制和处理,就是因为动画效果,让开发时会出现一些意料之外的状况。

假设我们现在有一个上传照片的场景,我们每一个 ViewHolder 带有一张图片,然后上面有一个进度条,进度根据上传进度实时返回。如果你使用 notifyItemChange() 来更新动画的话,那么会有两个问题:

  1. 第一,你会发现每刷新一次,整个布局都会闪动一下。
    • 如果我们多次调用 notifyItemChange() 方法,条目会刷新多次吗?
  2. 第二,这个进度的数值我需要怎么传递才好呢?在 ViewHolder 的 Bean 对象中添加一个 progress 临时字段?
    • 其实可以通过 notifyItemChanged(int position, @Nullable Object payload). 每次在onbind时拿取出 payloads 集合中最后一个值(最新的进度)
    • 那么,payload 参数怎么传递到 ViewHolder 中?

另外针对局部刷新,还有两个问题:

  1. 第一,notifyItemChange() 和 真正的局部刷新 同一个位置,ViewHolder 还是同一个对象吗?
  2. 第二,局部刷新是没有设置动画效果吗?

带着这些问题,开始 RecyclerView 这一部分源码探索,接下来的所有源码基于 Android API 27 Platform。

一、notifyItemChange() 第二个参数

上面说了半天 RecyclerView 真正的局部刷新,但是,到底怎么就是局部刷新呢?其实很简单,看看 notifyItemChange(int position) 另外一个重载函数。

   public final void notifyItemChanged(int position, @Nullable Object payload) {
        mObservable.notifyItemRangeChanged(position, 1, payload);
    }

同时,在 Adapter 中,与 onBindViewHolder(@NonNull VH holder, int position)相对应,也有一个重载函数。默认实现就走上面这个方法。

    public void onBindViewHolder(@NonNull VH holder, int position,
            @NonNull List<Object> payloads) {
        onBindViewHolder(holder, position);
    }

其实对于第一个额外问题(如果我们多次调用 notifyItemChange() 方法,条目会刷新多次吗?)

  • 从上面两个方法中,我们就能猜到一些答案,多次调用应该只会回调刷新一次
    • 你看传入的 payload 是一个 Object,但是到 onBindViewHolder() 方法时参数却成了一个集合,那应该就是有合并的操作。
  • 另外再从性能上说
    • 连着 notify 多次,就重新 measure layout 多次的话,这个开销也是很大并且没有必要(RecyclerView 严格控制 requestLayout() 方法调用),真没必要

二、RecyclerView.onItemRangeChanged

  • 在调用 notifyItemChange() 方法(不管一个参数还是两个个参数)之后
    • 最后都会走到 notifyItemRangeChanged(int positionStart, int itemCount,@Nullable Object payload)
    • 最后回调到 RecyclerViewDataObserver. onItemRangeChanged() 方法。
//RecyclerView
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
    // fallback to onItemRangeChanged(positionStart, itemCount) if app
    // does not override this method.
    onItemRangeChanged(positionStart, itemCount);
}

//RecyclerViewDataObserver
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}

// AdapterHelper 
void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}

在该方法中,有一个 triggerUpdateProcessor() 方法,它本质上说,就是去请求重新布局。

那就是说,这里只有 if 条件成立,才会去 requestLayout() ,接下来,搞清楚什么时候 if 成立,就能回答这个前置问题。

2.1 AdapterHelper.onItemRangeChanged 判断

/**
 * @return True if updates should be processed.
 */
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;
}

 // 构造UpdateOp
 UpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
        this.cmd = cmd;
        this.positionStart = positionStart;
        this.itemCount = itemCount;
        this.payload = payload;
}
  1. 第一,size==1 说明就第一次调用是才返回 true 才触发 requestLayout() ;
  2. 第二,payload 参数在这里被包装为对象,放入 mPendingUpdates 这个集合中

第一个发现,彻底证明上诉猜测是正确的,即使你调用 notify 多次,其实只有第一次会触发 requestLayout()

2.2 requestLayout()里的measure layout

既然有 requestLayout() 调用,那么就回到 onMeasure() 和 onLayout() 这些方法中。

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }
        ...
    }
    ...
}
  • 假设我们 RecyclerView 布局就是两个 match_parent 或者有一个精确值,那么执行的代码片段就是这样

主要看layout:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    dispatchLayout();
    mFirstLayoutComplete = true;
}

void dispatchLayout() {
    ...
    mState.mIsMeasuring = false;
    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();
}

在 RecyclerView 的 onLayout() 方法中,一共执行三大步骤。从命名上已经能清楚看懂。对于三个 step ,每个方法上面都有详细注释。翻译过来就是说:

  1. 第一步时,处理 Adapter 更新,决定执行的动画效果,保存当前 Views 的信息,最后,如果必要的话,预测布局并保存相关信息;
  2. 第二步时,根据最终状态执行布局,并且可能执行多次;
  3. 第三步,保存 View 信息,执行动画,最后做一些清除重置操作。

2.2.1 dispatchLayoutStep1()

处理 Adapter 更新,决定执行的动画效果,保存当前 Views 的信息,最后,如果必要的话,预测布局并保存相关信息。

private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    startInterceptRequestLayout();
    //1.更新 mRunSimpleAnimations 和 mRunPredictiveAnimations flag 其实还有其他一些骚操作
    processAdapterUpdatesAndSetAnimationFlags();
    //2.mInPreLayout 设置为 true 后面有用
    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());
            //5.保存动画信息相关
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
                //3.如果holder确定要更新,就把它添加到 oldChangeHolders 集合中
                long key = getChangedHolderKey(holder);
                mViewInfoStore.addToOldChangeHolders(key, holder);
            }
        }
    }
    if (mState.mRunPredictiveAnimations) {
        ...
        //4.很重要,LayoutManager 开始工作
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = didStructureChange;

        for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
            final View child = mChildHelper.getChildAt(i);
            final ViewHolder viewHolder = getChildViewHolderInt(child);
            if (viewHolder.shouldIgnore()) {
                continue;
            }
            if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                ...
                //5.保存动画信息相关
                mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
            }
        }
        ...
    } ...
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    mState.mLayoutStep = State.STEP_LAYOUT;
}
  1. 第一点,更新动画相关标识位 mRunSimpleAnimations ,mRunPredictiveAnimations,后面的操作都依赖它们
  2. 第二点,将mInPreLayout 的状态和 mRunPredictiveAnimations 同步, 设置true。这个在后面的步骤中也需要使用
  3. 第三点,保存需要更新的 ViewHolder 到 oldChangeHolder 集合中
  4. 第四点,调用 LayoutManager. onLayoutChildren()。 很重要,LayoutManager 开始工作
  5. 第五点,保存相关动画信息。

2.2.1.1 第一步 processAdapterUpdatesAndSetAnimationFlags()

/RecyclerView
private void processAdapterUpdatesAndSetAnimationFlags() {
    ...
    // simple animations are a subset of advanced animations (which will cause a
    // pre-layout step)
    // If layout supports predictive animations, pre-process to decide if we want to run them
    ...
        mAdapterHelper.preProcess();
    ...
    boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
    // 通常情况就是 ture
    mState.mRunSimpleAnimations = mFirstLayoutComplete
            && mItemAnimator != null
            && (mDataSetHasChangedAfterLayout
            || animationTypeSupported
            || mLayout.mRequestedSimpleAnimations)
            && (!mDataSetHasChangedAfterLayout
            || mAdapter.hasStableIds());
    // 通常情况就是 ture
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
            && animationTypeSupported
            && !mDataSetHasChangedAfterLayout
            && predictiveItemAnimationsEnabled();
}

//AdapterHelper
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();
}
 //AdapterHelper
private void postponeAndUpdateViewHolders(UpdateOp op) {
    if (DEBUG) {
        Log.d(TAG, "postponing " + op);
    }
    mPostponedList.add(op);
    switch (op.cmd) {
        ...
        case UpdateOp.UPDATE:
            mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
            break;
        default:
            throw new IllegalArgumentException("Unknown update op type for " + op);
    }
}


  1. 首先执行 mAdapterHelper.preProcess() 后,会将刚刚上文说到的onItemRangeChanged() 方法中的 payload 包装成的 UpdateOp 对象,到这里,要开始处理这个对象
  2. cmd 对应我们的操作,这里就是 update,后面就是 notifyItemRangeChange() 方法中对应的参数。

AdapterHelper 最后会使用 callback 回调到 RecyclerView 中,在 RecyclerView 中执行 viewRangeUpdate() 方法。这个 callback 是 RecyclerView 在创建时就已经设置:

//RecyclerView 初始化是调用
void initAdapterManager() {
    mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
        ....
        @Override
        public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
            viewRangeUpdate(positionStart, itemCount, payload);
            mItemsChanged = true;
        }
    });
}

//RecyclerView
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) {
            // 很重要,在这里更新了 Flag 然后将 payload 传递到 Viewholder 中
            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);
}

在 viewRangeUpdate() 方法中:

  • holder.addFlags(ViewHolder.FLAG_UPDATE)
    • holder 加上 FLAG_UPDATE 标识,请先记住,这个标识很重要。
  • holder.addChangePayload(payload);
    • 然后,关键问题之一来了,payload 通过 addChangePayload() 方法直接加到对应 holder 中

2.2.1.2 第三步,保存需要更新的 ViewHolder 到 oldChangeHolder 集合中

processAdapterUpdatesAndSetAnimationFlags() 后部分,会设置 mRunSimpleAnimations 和 mRunPredictiveAnimations 两个标识为 true

从而会触发第三步:

 if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                && !holder.shouldIgnore() && !holder.isInvalid()) {
            //3.如果holder确定要更新,就把它添加到 oldChangeHolders 集合中
            long key = getChangedHolderKey(holder);
            mViewInfoStore.addToOldChangeHolders(key, holder);
        }

2.2.1.3 调用 LayoutManager. onLayoutChildren()。 很重要,LayoutManager 开始工作

在 LayoutManger 的 onLayoutChildren() 中有大量代码,这里就看我们关心的两行代码,其实就是两个:

  1. detachAndScrapAttachedViews()
  2. fill()方法。
//LinearLayoutManger
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ...
     detachAndScrapAttachedViews(recycler);
     fill(recycler, mLayoutState, state, false);
    ...
}
  • detachAndScrapAttachedViews() 这个方法最后会反向遍历所有 View 依次调用 Recycler 的 scrapView() 方法
    • 关于 Recycler ,可以说是RecyclerView 的核心之一
    void scrapView(View view) {
        final ViewHolder holder = getChildViewHolderInt(view);
        // 如果不需要更新 放到 mAttachedScrap 中
        if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
            ...
            holder.setScrapContainer(this, false);
            mAttachedScrap.add(holder);
        } else {
             // 需要更新 放到 mChangedScrap 中
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this, true);
            mChangedScrap.add(holder);
        }
    }
  1. 如果不需要更新,会加到 mAttachedScrap 全家桶中
  2. 需要更新的,就会放到 mChangedScrap 中。
  3. 为什么要加入到这些集合中呢,因为后面 fill() 的时候会通过这些集合去找对应的 holder,生产对应的 View 最后真正添加到 RecyclerView 控件中
  • fill()方法中,最终会调用 tryGetViewHolderForPositionByDeadline()方法找到 ViewHolder,拿到对应 View,然后 addView()
//Recycler
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ...
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 在 dispatchLayoutStep1 中 设置为   mState.mInPreLayout = mState.mRunPredictiveAnimations
    // 0. 从 mChangedScrap 集合中寻找
    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 offsetPosition = mAdapterHelper.findPositionOffset(position);
        ...
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2. Find from scrap/cache via stable ids, if exists
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
        // 3. 从 mViewCacheExtension 查找,mViewCacheExtension 默认为 null 
        if (holder == null && mViewCacheExtension != null) {
            // We are NOT sending the offsetPosition because LayoutManager does not
            // know it.
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
                ...
            }
        }
        if (holder == null) { // fallback to pool
            ...
            //4. 从 RecycledViewPool 中查找
            holder = getRecycledViewPool().getRecycledView(type);
            ...
        }
        if (holder == null) {
            ...
            //5. 老实创建
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            ...
        }
    }

holder 的查找是一个漫长过程:

  • **注意这里第 0 步,只有 isPreLayout() 为 true 才会从 mChangedScrap 集合中查找 ViewHolder **
    • 在 dispatchLayoutStep1() 中,mState.mInPreLayout = mState.mRunPredictiveAnimations 默认会设置为 true ,所以会执行到这里。
  • 第一步从 mAttachedScrap mHiddenViews mCachedViews 这些集合中查找;
  • 第二步通过 独立 id 再次查找;
  • 第三步,可能的话,通过 mViewCacheExtension 拓展查找,这个可以通过 RecyclerView 设置;
  • 第四部,从 RecycledViewPool 中查找;
  • 最后,通过 adapter 创建。

2.2.2 dispatchLayoutStep2()

根据最终状态执行布局,并且可能执行多次。

private void dispatchLayoutStep2() {
    ...
    // 注意,这里 mInPreLayout 设置为 false 
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;
    mPendingSavedState = null;
    ...
}

相比第一步预备,第二步其实来得简单得多,核心就是

  1. 将 mInPreLayout 设置为 false
  2. 然后重新调用 LayoutManager 的 onLayoutChildren() 方法

过程就如上分析,但是,因为这里 mInPreLayout 字段为 false ,而我们之前修改的 ViewHolder 是被添加到 mChangedScrap 集合中,但是因为 mInPreLayout 为 false, 它不会再去 mChangedScrap 查找ViewHolder

所以 旧的 ViewHolder 无法被复用,它会从下面步骤中获取ViewHolder 返回。也就是说,当我们通常使用 notifyItemChangge(pisition) 一个参数的方法之后,刷新时它使用的不是同一个 ViewHolder

2.2.3 dispatchLayoutStep3()

保存 View 信息,执行动画效果,最后做一些清除操作。

private void dispatchLayoutStep3() {
    ...
    if (mState.mRunSimpleAnimations) {
        // Step 3: Find out where things are now, and process change animations.
        // traverse list in reverse because we may call animateChange in the loop which may
        // remove the target view holder.
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            ...
            long key = getChangedHolderKey(holder);
            //获取当前 holder 动画信息
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPostLayoutInformation(mState, holder);
            
            //获取 olderViewHolder
            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);
                    // 这里一存一取 完成信息覆盖
                    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);
            }
        }

        // 重点,真正开始执行添加的动画效果
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    //回收 和 重置
   ...
}

在 dispatchLayoutStep1() 中,我们保存了一些信息,现在终于要派上用场。

关于动画相关逻辑,已经添加相关注释。

最后需要注意,如果需要执行动画,将会执行 animateChange() 方法,该方法会完成动画相关的创建,并不会直接执行,而是到最后 mViewInfoStore.process(mViewInfoProcessCallback)调用,才开始真正的获取相关动画信息,并执行。

// RecyclerView 
private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
        @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
        boolean oldHolderDisappearing, boolean newHolderDisappearing) {
    oldHolder.setIsRecyclable(false);
    if (oldHolderDisappearing) {
        addAnimatingView(oldHolder);
    }
    if (oldHolder != newHolder) {
        if (newHolderDisappearing) {
            addAnimatingView(newHolder);
        }
        // 这里很有意思,有点儿像绕口令,这种设定是为了后面做相关释放
        oldHolder.mShadowedHolder = newHolder;
        addAnimatingView(oldHolder);
        mRecycler.unscrapView(oldHolder);
        newHolder.setIsRecyclable(false);
        newHolder.mShadowingHolder = oldHolder;
    }
    if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
        // 到这里真正执行相关动画
        postAnimationRunner();
    }
}


// DefaultItemAnimator
@Override
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
        int fromX, int fromY, int toX, int toY) {
    if (oldHolder == newHolder) {
        // 如果新旧 holder 相同时,就添加一个 move 动画效果 
        // 如果偏移量为 0 直接返回 false 不执行动画效果
        return animateMove(oldHolder, fromX, fromY, toX, toY);
    }
    // 如果新旧不一样 那么就需要 计算出偏移量,然后创建一个 ChangeInfo 
    final float prevTranslationX = oldHolder.itemView.getTranslationX();
    final float prevTranslationY = oldHolder.itemView.getTranslationY();
    final float prevAlpha = oldHolder.itemView.getAlpha();
    resetAnimation(oldHolder);
    int deltaX = (int) (toX - fromX - prevTranslationX);
    int deltaY = (int) (toY - fromY - prevTranslationY);
    // recover prev translation state after ending animation
    oldHolder.itemView.setTranslationX(prevTranslationX);
    oldHolder.itemView.setTranslationY(prevTranslationY);
    oldHolder.itemView.setAlpha(prevAlpha);
    if (newHolder != null) {
        // carry over translation values
        resetAnimation(newHolder);
        newHolder.itemView.setTranslationX(-deltaX);
        newHolder.itemView.setTranslationY(-deltaY);
        newHolder.itemView.setAlpha(0);
    }
    // 添加到 动画 集合中,等待接下来真正运行
    mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
    return true;
}

@Override
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
        int toX, int toY) {
    final View view = holder.itemView;
    fromX += (int) holder.itemView.getTranslationX();
    fromY += (int) holder.itemView.getTranslationY();
    resetAnimation(holder);
    int deltaX = toX - fromX;
    int deltaY = toY - fromY;
    // 如果平移偏移量都是 0 那么就不执行动画效果
    if (deltaX == 0 && deltaY == 0) {
        dispatchMoveFinished(holder);
        //false 不会触发动画效果
        return false;
    }
    if (deltaX != 0) {
        view.setTranslationX(-deltaX);
    }
    if (deltaY != 0) {
        view.setTranslationY(-deltaY);
    }
    // 添加动画效果 等待执行
    mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
    return true;
}

上面三个方法,分别是具体的动画添加过程,其中有些注意点,如果 oldHolder 和 newHolder 相等,并且相关偏移量为零,那么不会添加和执行相关动画效果。

  • 如果有相关动画效果,会创建加入到 DefaultItemAnimator 的集合中,然后 mItemAnimator.animateChange() 方法返回 true ,最后调用 postAnimationRunner() 方法执行。

  • 在 postAnimationRunner() 方法的 Runnable 中最后会调用 mItemAnimator.runPendingAnimations() ,最后将会执行到 animateChangeImpl() 方法

//DefaultItemAnimator 
void animateChangeImpl(final ChangeInfo changeInfo) {
    final ViewHolder holder = changeInfo.oldHolder;
    final View view = holder == null ? null : holder.itemView;
    final ViewHolder newHolder = changeInfo.newHolder;
    final View newView = newHolder != null ? newHolder.itemView : null;
    //旧View存在的话,执行相关动画
    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);
                 //回调 RecyclerView 
                dispatchChangeFinished(changeInfo.oldHolder, true);
                mChangeAnimations.remove(changeInfo.oldHolder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }
    //新 View 执行相关动画
    if (newView != null) {
        final ViewPropertyAnimator newViewAnimation = newView.animate();
        mChangeAnimations.add(changeInfo.newHolder);
        newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
                .alpha(1).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                        dispatchChangeStarting(changeInfo.newHolder, false);
                    }
                    @Override
                    public void onAnimationEnd(Animator animator) {
                        //回收释放
                        newViewAnimation.setListener(null);
                        newView.setAlpha(1);
                        newView.setTranslationX(0);
                        newView.setTranslationY(0);
                        //回调 RecyclerView 
                        dispatchChangeFinished(changeInfo.newHolder, false);
                        mChangeAnimations.remove(changeInfo.newHolder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }
}

// RecyclerView inner 
private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {

    ItemAnimatorRestoreListener() {
    }

    @Override
    public void onAnimationFinished(ViewHolder item) {
        item.setIsRecyclable(true);
        //前面 ViewHolder 绕口令式设置,在这里做最后释放
        if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
            item.mShadowedHolder = null;
        }
        // always null this because an OldViewHolder can never become NewViewHolder w/o being
        // recycled.
        item.mShadowingHolder = null;
        if (!item.shouldBeKeptAsChild()) {
            if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
                removeDetachedView(item.itemView, false);
            }
        }
    }
} 

在 animateChangeImpl() 方法中,分别为 oldView 和 newView 执行相关动画,最后回调到 RecyclerView 中的 onAnimationFinished() 方法中,完成对 ViewHolder 之前动画相关联 holder 的释放。

2.3 回头看之前提出的局部刷新的两个问题

基于上面第三步分析的结论,如果 oldHolder 和 newHolder 相等,并且偏移量为零,那么不会添加和执行相关动画效果,再结合实际情况

  1. 我们不妨大胆猜测第一个问题的答案,它们使用的是同一个 ViewHolder 对象
  2. 接着针对第二个问题,如果使用同一个对象,并且偏移值为 0 ,那么就不会执行相关动画效果

但是,这个结论似乎和我们分析第二步时得出的结论有出入:而我们之前修改的 ViewHolder 是被添加到 mChangedScrap 集合中,但是因为 mInPreLayout 此时设置为 false, 它不会再去 mChangedScrap 查找ViewHolder,也就是说,当我们通常使用 notifyItemChangge() 一个参数的方法之后,它使用的不是同一个 ViewHolder。

到底是哪里错了呢?其实都没错,上面说的是使用 notifyItemChangge(position) 一个参数的方法时的情况

2.3.1 局部刷新时,我们使用的可是两个参数的方法。

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    // 如果不需要更新 放到 mAttachedScrap 中
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
        // 没有更新 或者 可以被复用
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        ...
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
         // 需要更新 放到 mChangedScrap 中
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}

//RecyclerView
boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
    return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
            viewHolder.getUnmodifiedPayloads());
}

//ItemAnimator
@Override
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
        @NonNull List<Object> payloads) {
    return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}

针对第二步中,调用的 scrapView() 方法我们再看一次,我们可以看到,在 if 判断中,如果 holder 确实需要被更新,那么它也可能被添加到 mAttachedScrap 集合中,只要 canReuseUpdatedViewHolder(holder) 这个方法能返回 true 。

当我们使用局部刷新,payload 不为空 ,这个时候,如果 ViewHolder 需要更新,它的 更新标识 的确会被加入,但是同时canReuseUpdatedViewHolder() 也会返回 true ,所以,这个时候 ViewHolder 不会被添加到 mChangedScrap 集合中,而是加入 mAttachedScrap 集合中, 最终保证了使用同一个ViewHolder

三、结论

  1. 当你使用局部刷新notifyItemChanged(int position, @Nullable Object payload)时,前后都是同一个 ViewHolder ,如果位置没有变化,就不会执行动画效果;
  2. 而当你不使用局部刷新时notifyItemChanged(int position),使用的不是同一个 ViewHolder ,不管位置是否变化,都会执行相关动画,所以你看到的 itemView 会闪烁一下。
  3. 当我们多次调用 notifyItemChange() 方法时,也不会多次触发 requestLayout() 和回调 bindViewHolder()

参考文献

  • Android RecyclerView 局部刷新分析

你可能感兴趣的:((4.0.22.11)Android RecyclerView 局部刷新分析)