RecyclerView局部刷新和原理介绍

一、引言

Android早期实现列表功能的控件是ListView,后来google推出了RecyclerView替代ListView。RecyclerView相对ListView有一些优势,其中一个是局部刷新,本文主要围绕它进行介绍。

二、局部刷新的正确使用姿势

对于局部刷新,我的理解是notifyDataChanged()方法是全部刷新,notifyItemChanged、notifyItemInserted、notifyItemRemoved等一系列方法只对相应位置的item进行刷新。但当我把之前代码里的notifyDataChanged()方法替换为使用notifyItemChanged(int position)方法,来完成点赞按钮状态替换时,却遇到了图片闪烁的问题,如下图所示:
notifyItemChanged()导致图片闪烁
经测试发现,notifyItemChanged(int position)方法导致Adapter的onBindViewHolder(@NonNull VH holder, int position)方法得到调用,但每次调用时holder和holder.itemView的hashCode值并不相同,也就是notifyItemChanged(int position)方法调用时,RecyclerView更换了position位置的View。
notifyItemChanged(int)
经过google发现,notifyItemChanged(int position)有一个重载方法,notifyItemChanged(int position, @Nullable Object payload),其实notifyItemChanged(int position)本质上调用的就是notifyItemChanged(int position, null)。这个方法可以用来实现更深一层的局部刷新:Item内部View的局部刷新。方法说明中关于payload介绍如下:

Client can optionally pass a payload for partial change. These payloads will be merged and may be passed to adapter’s onBindViewHolder(RecyclerView.ViewHolder, int, List) if the item is already represented by a ViewHolder and it will be rebound to the same ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing payloads on that item and prevent future payload until onBindViewHolder(RecyclerView.ViewHolder, int, List) is called. Adapter should not assume that the payload will always be passed to onBindViewHolder(), e.g. when the view is not attached, the payload will be simply dropped.

我把notifyItemChanged(int position)更改为notifyItemChanged(int position, @Nullable Object payload),果然没有再出现图片闪烁的问题,代码如下:

adapter.notifyItemChanged(position, VideoRcmdListAdapter.UPDATE_REASON_LIKE)

override fun onBindViewHolder(holder: BaseViewHolder<FeedCard>, position: Int, payloads: MutableList<Any>) {
        val updateByPayload = bindPosViewWithPayLoads(holder, items[position], payloads)
        if (!updateByPayload) {
            onBindViewHolder(holder, actualPosition)
        }
    }

    private fun bindPosViewWithPayLoads(holder: BaseViewHolder<FeedCard>, item: FeedCard, payloads: MutableList<Any>): Boolean {
        var updateByPayload = false
        for (payload in payloads) {
            if (payload == VideoRcmdListAdapter.UPDATE_REASON_LIKE && holder is FeedVideoRcmdViewHolder && item.videoCard != null) {
                holder.updateLikeCheckBos(item.videoCard.liked, item.videoCard.favorites)
                updateByPayload = true
            }
        }
        return updateByPayload
    }

综合而言,如果是item内部某部分更改,比如像这里只是更改喜欢按钮的状态,使用payload;如果是item整个都更改,比如item1更换为了item2,那直接使用notifyItemChanged(int position)就好。

三、局部刷新的原理

先说结论吧:
Adapter.notifyItemChanged(int position, @Nullable Object payload)方法会导致RecyclerView的onMeasure()和onLayout()方法调用。在onLayout()方法中会调用dispatchLayoutStep1()、dispatchLayoutStep2()和dispatchLayoutStep3()三个方法,其中dispatchLayoutStep1()将更新信息存储到ViewHolder中,dispatchLayoutStep2()进行子View的布局,dispatchLayoutStep3()触发动画。在dispatchLayoutStep2()中,会通过DefaultItemAnimatorcanReuseUpdatedViewHolder()方法判断position处是否复用之前的ViewHolder,如果调用notifyItemChanged()时的payload不为空,则复用;否则,不复用。在dispatchLayoutStep3()中,如果position处的ViewHolder与之前的ViewHolder相同,则执行DefaultItemAnimator的move动画;如果不同,则执行DefaultItemAnimatorchange动画,旧View动画消失(alpha值从1到0),新View动画展现(alpha值从0到1),这样就出现了闪烁效果。

再说一点自己的认识:
1.可以直接调试
由于项目中RecyclerView都是通过"compile com.android.support:recyclerview-v7:27.1.1"方式引入的,所以RecyclerView的代码是可以直接调试的,不然直接只看代码分析执行流程,还真是很难看下去。
2.RecyclerView、LinearLayoutManager、ChildHelper、AdapterHelper中都有静态DEBUG变量,用来控制日志输出的代码,默认为false,但我通过反射将其设置为true,仍然没有日志输出。这是因为Android studio在代码编译时将永不会执行的代码删除了,如果不想被编译器删除,我能想到的复杂方法只有通过aop方式了。当然,直接把代码拷入到项目中,将DEBUG默认值设为true更简单。
3.RecyclerView的代码很复杂,自己只看懂了一小部分,但也看到用了很多设计模式和数据结构,用得都很恰当,优雅,比如随处可见的享元模式,ArrayMap、LongSparseArray等,很值得看看。

3.0 前提

这部分以第二部分的例子入手,主要介绍下RecyclerView使用LinearLayoutManager和DefaultItemAnimator,RecyclerView的layout_width和layout_height都声明为"match_parent",RecyclerView未设置hasFixedSize且Adapter未设置HasStableIds时,notifyItemChanged(int position, @Nullable Object payload)的执行流程。notifyItemChanged()调用前页面已经刷新完毕,调用后短时间内没有其他操作。

3.1 RecyclerView与Adapter建立观察者模式

从adapter.notifyItemChanged(int position, @Nullable Object payload)方法入手

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

其中mObservable为AdapterDataObservable类型的对象,它的notifyItemRangeChanged()方法如下:

 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);
     }            
}

直接对mObservers列表进行遍历,并调用其onItemRangeChanged()方法。向mObservers中添加Observer的方法为registerObserver()方法。

    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

RecyclerView中最终调用registerObserver的地方为setAdapterInternal()方法,这个方法又被setAdapter()和swapAdapter()调用,registerObserver()方法传入的参数为RecyclerView的mObserver变量。
总结而言,RecyclerView通过调用setAdapter(adapter)方法,与adapter建立观察者模式。当数据更改时,开发者通过adapter的notifyXXX()方法通知RecyclerView的mObserver变量进行UI更新。

3.2 onItemRangeChanged()

RecyclerView中的mObserver是RecyclerViewDataObserver类型的变量,看下它的onItemChanged()方法。

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

直接调用了mAdapterHelper的onItemRangeChanged()方法。

 boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
        return mPendingUpdates.size() == 1;
    }

该方法将数据更新信息封装为UpdateOp对象,并将其添加到mPendingUpdates列表中,如果列表中数目为1,则返回true,否则false。在3.0的前提下,由于是在列表已经刷新完成的情况下调用notifyItemChanged(),所以应该返回true,会调用triggerUpdateProcessor方法。
再看下triggerUpdateProcessor()方法

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

由于未设置hasFixedSize,直接调用requestLayout()方法。requestLayout()方法会导致RecyclerView的onMeasure()和onLayout()方法得到调用。看下RecyclerView的onMeasure()方法。

    @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);

            /**
             * This specific call should be considered deprecated and replaced with
             * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
             * break existing third party code but all documentation directs developers to not
             * override {@link LayoutManager#onMeasure(int, int)} when
             * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
             */
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }

            // ...省略无关代码
        } else {
        // ...省略无关代码
    }

mLayout是LinearLayoutManager类的对象,LinearLayoutManager的isAutoMeasureEnabled()为true;RecyclerView的宽高被设置为match_parent,因此widthMode和heightMode都为MeasureSpec.EXACTLY,进而measureSpecModeIsExactly为true。整个onMeasure()方法其实只调用了LayoutManager的onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec)方法,最终调用的是RecyclerView的defaultOnMeasure()方法,在RecyclerView宽高被设置为match_parent时等价于:

    /**
     * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios
     * where this RecyclerView is otherwise lacking better information.
     */
    void defaultOnMeasure(int widthSpec, int heightSpec) {
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = View.MeasureSpec.getSize(widthSpec);
        final int height = View.MeasureSpec.getSize(heightSpec);

        setMeasuredDimension(width, height);
    }

因此图片闪烁问题的原因只能去onLayout()方法中查找。RecyclerView的onLayout()方法直接调用了dispatchLayout()方法,去除无关代码后如下:

    void dispatchLayout() {
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {
            dispatchLayoutStep2();
        } else {
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

在3.0的前提下,mState.mLayoutStep为State.STEP_START,因此会依序执行dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()三个方法。

3.3 dispatchLayoutStep1()

先看下dispatchLayoutStep1()的简化代码,

    /**
     * The first step of a layout where we;
     * - process adapter updates
     * - decide which animation should run
     * - save information about current views
     * - If necessary, run predictive layout and save its information
     */
    private void dispatchLayoutStep1() {
        mViewInfoStore.clear();
        processAdapterUpdatesAndSetAnimationFlags(); //① ②
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        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));
                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);
                    // This is NOT the only place where a ViewHolder is added to old change holders
                    // list. There is another case where:
                    //    * A VH is currently hidden but not deleted
                    //    * The hidden item is changed in the adapter
                    //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                    // When this case is detected, RV will un-hide that view and add to the old
                    // change holders list.
                    mViewInfoStore.addToOldChangeHolders(key, holder);// ③
                }
            }
        }
        if (mState.mRunPredictiveAnimations) { 
            // Step 1: run prelayout: This will use the old positions of items. The layout manager
            // is expected to layout everything, even removed items (though not to add removed
            // items back to the container). This gives the pre-layout position of APPEARING views
            // which come into existence as part of the real layout.

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // temporarily disable flag because we are asking for previous layout
            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)) {
                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                    boolean wasHidden = viewHolder
                            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (!wasHidden) {
                        flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    if (wasHidden) {
                        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                    } else {
                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                    }
                }
            }
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

对于第二部分的例子而言,该方法做了四件事,这四件事代码作者都写在方法的注释里了:
* - process adapter updates 处理Adapter更新,将3.2中AdapterHelper里的mPendingUpdates中的内容存储到ViewHolder中
* - decide which animation should run 确定会运行的动画
* - save information about current views 记录当前Views的信息
* - If necessary, run predictive layout and save its information 运行predictive layout,并保存它的信息
看下这四步都分别做了什么:

3.3.1 处理Adapter更新和确定会运行的动画

这两步都在processAdapterUpdatesAndSetAnimationFlags()方法中,

    /**
     * Consumes adapter updates and calculates which type of animations we want to run.
     * Called in onMeasure and dispatchLayout.
     * <p>
     * This method may process only the pre-layout state of updates or all of them.
     */
    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
        if (predictiveItemAnimationsEnabled()) { // ① 
            mAdapterHelper.preProcess(); // ② 
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }
        boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;  // true
        mState.mRunSimpleAnimations = mFirstLayoutComplete  // true
                && mItemAnimator != null  // 默认为DefaultItemAnimator,true
                && (mDataSetHasChangedAfterLayout
                || animationTypeSupported  // true
                || mLayout.mRequestedSimpleAnimations)
                && (!mDataSetHasChangedAfterLayout // true
                || mAdapter.hasStableIds()); // ③ true
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations  // true
                && animationTypeSupported // true
                && !mDataSetHasChangedAfterLayout // true
                && predictiveItemAnimationsEnabled(); // ④ true
    }

① 是判断是否允许predictiveItemAnimations,predictive animation介绍,对于3.0前提下的LinearLayoutManager而言为true。
② 将3.2中AdapterHelper里的mPendingUpdates中的内容存储到ViewHolder中。
重点看下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();
    }

从3.2处的分析可知,mPendingUpdates列表中只包含一个cmd为UpdateOp.UPDATE的UpdateOp,因此只调用了一次applyUpdate(op)方法并将mPendingUpdates清空,看下AdapterHelper.applyUpdate()方法:

    private void applyUpdate(UpdateOp op) {
        int tmpStart = op.positionStart;
        int tmpCount = 0;
        int tmpEnd = op.positionStart + op.itemCount;
        int type = -1;
        for (int position = op.positionStart; position < tmpEnd; position++) {
            RecyclerView.ViewHolder vh = mCallback.findViewHolder(position); 
            // vh不为null,true
            if (vh != null || canFindInPreLayout(position)) { // deferred
                if (type == POSITION_TYPE_INVISIBLE) {
                    UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
                            op.payload);
                    dispatchAndUpdateViewHolders(newOp);
                    tmpCount = 0;
                    tmpStart = position;
                }
                type = POSITION_TYPE_NEW_OR_LAID_OUT; // POSITION_TYPE_NEW_OR_LAID_OUT值为1
            } 
            tmpCount++;
        }
        // 省略部分代码
        if (type == POSITION_TYPE_INVISIBLE) {
            dispatchAndUpdateViewHolders(op);
        } else {
            postponeAndUpdateViewHolders(op);
        }
    }

mCallback变量是在RecyclerView构造函数中初始化AdapterHelper变量时创建的,由于执行applyUpdate的View是显示的,所以vh值不为null。因此type为POSITION_TYPE_NEW_OR_LAID_OUT,进而调用了postponeAndUpdateViewHolders()方法。

    private void postponeAndUpdateViewHolders(UpdateOp 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);
        }
    }

postponeAndUpdateViewHolders()做了两件事,① 将UpdateOp存入mPostponedList中,② 调用mCallback的markViewHoldersUpdated方法,markViewHoldersUpdated()方法如下:

            @Override
            public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
                viewRangeUpdate(positionStart, itemCount, payload); // ①
                // 此处可以回到processAdapterUpdatesAndSetAnimationFlags和dispatchLayoutStep1中看下mItemsChanged使用的地方
                mItemsChanged = true;  // ②
            }

做了两件事,① 调用viewRangeUpdate()方法,② 将mItemsChanged变量置为true。

    /**
     * Rebind existing views for the given range, or create as needed.
     *
     * @param positionStart Adapter position to start at
     * @param itemCount Number of views that must explicitly be rebound
     */
    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);
    }

viewRangeUpdate()方法完成了将mPendingUpdates列表里的更新存储到相应的ViewHolder中的功能:添加ViewHolder.FLAG_UPDATE的flag,表示该ViewHolder要发生改变,之后该viewHolder的isUpdated()和needsUpdate()方法将返回true;将payload保存到holder的mPayloads列表中。
回到processAdapterUpdatesAndSetAnimationFlags()中,由于mItemsChanged在markViewHoldersUpdated()方法中被置为true,所以animationTypeSupported变量为true,同时由于mFirstLayoutComplete为true,mItemAnimator默认为DefaultItemAnimator,不为null,mDataSetHasChangedAfterLayout为false,所以③ 处mState.mRunSimpleAnimations也为true。在④处,mState.mRunPredictiveAnimations也为true。

        boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;  // true
        mState.mRunSimpleAnimations = mFirstLayoutComplete  // true
                && mItemAnimator != null  // 默认为DefaultItemAnimator,true
                && (mDataSetHasChangedAfterLayout
                || animationTypeSupported  // true
                || mLayout.mRequestedSimpleAnimations)
                && (!mDataSetHasChangedAfterLayout // true
                || mAdapter.hasStableIds()); // ③ true
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations  // true
                && animationTypeSupported // true
                && !mDataSetHasChangedAfterLayout // true
                && predictiveItemAnimationsEnabled(); // ④ true
3.3.2 记录Views信息

回到dispatchLayoutStep1()中

        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

        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);
                    // This is NOT the only place where a ViewHolder is added to old change holders
                    // list. There is another case where:
                    //    * A VH is currently hidden but not deleted
                    //    * The hidden item is changed in the adapter
                    //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                    // When this case is detected, RV will un-hide that view and add to the old
                    // change holders list.
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
            }
        }

由于processAdapterUpdatesAndSetAnimationFlags()方法中mState.mRunSimpleAnimations为true,且viewRangeUpdate()方法中将
mItemsChanged变量设置为true,所以mState.mTrackOldChangeHolders变量为true,mState.mInPreLayout也为true。由于mState.mRunSimpleAnimations为true,会对RecyclerView中的ChildView进行遍历,从view中取出holder,并记录所有holder到preLayout信息和要发生改变的holder的信息。

3.3.2.1 记录preLayout信息

看下ViewInfoStore的addPreLayout()方法,将holder和animationInfo存入到mLayoutHolderMap中。

    /**
     * Adds the item information to the prelayout tracking
     * @param holder The ViewHolder whose information is being saved
     * @param info The information to save
     */
    void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            // mLayoutHolderMap为ArrayMap类型
            mLayoutHolderMap.put(holder, record); 
        }
        record.preInfo = info;
        record.flags |= FLAG_PRE;
    }
3.3.2.2 记录要发生改变等holder等信息

对于3.3.1中viewRangeUpdate()方法中处理过的ViewHolder,holder.isUpdated()为true,holder.isRemoved()、holder.shouldIgnore()、holder.isInvalid()都为false,所以会存入到ViewInfoStore的mOldChangedHolders中,这个会在dispatchLayoutStep3()中用到。

                if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                        && !holder.shouldIgnore() && !holder.isInvalid()) {
                    long key = getChangedHolderKey(holder);
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
                
3.3.3 运行predictive layout,并保存它的信息
mState.mInPreLayout = mState.mRunPredictiveAnimations;
//... 省略部分代码
mLayout.onLayoutChildren(mRecycler, mState);

此处的mLayout为LinearLayoutManager类型的对象,看下LinearLayoutManager的onLayoutChildren()方法,省略无关代码后如下。

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // calculate anchor position and coordinate
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo); // ① 寻找锚点
            mAnchorInfo.mValid = true;
        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                        >= mOrientationHelper.getEndAfterPadding()
                || mOrientationHelper.getDecoratedEnd(focused)
                <= mOrientationHelper.getStartAfterPadding())) {
            // This case relates to when the anchor child is the focused view and due to layout
            // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
            // up after tapping an EditText which shrinks RV causing the focused view (The tapped
            // EditText which is the anchor child) to get kicked out of the screen. Will update the
            // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
            // the available space in layoutState will be calculated as negative preventing the
            // focused view from being laid out in fill.
            // Note that we won't update the anchor position between layout passes (refer to
            // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
            // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
            // child which can change between layout passes).
            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        }

        detachAndScrapAttachedViews(recycler);  // ② 将RecyclerView中的所有View detach并回收
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mIsPreLayout = state.isPreLayout();
        if (mAnchorInfo.mLayoutFromEnd) {
            // ...省略无关代码
        } else {
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false); // ③ 重新attach view,并铺满屏幕
            endOffset = mLayoutState.mOffset;
            final int lastElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;

            if (mLayoutState.mAvailable > 0) {
                extraForEnd = mLayoutState.mAvailable;
                // start could not consume all it should. add more items towards end
                updateLayoutStateToFillEnd(lastElement, endOffset);
                mLayoutState.mExtra = extraForEnd;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
            }
        }
        // ...省略无关代码
    }

onLayoutChildren()做了三件事:①寻找锚点;②将RecyclerView中的所有View detach并回收;③重新attach view,并铺满屏幕。
重点关注下②和③。
看下②中detachAndScrapAttachedViews()方法。

        /**
         * Temporarily detach and scrap all currently attached child views. Views will be scrapped
         * into the given Recycler. The Recycler may prefer to reuse scrap views before
         * other views that were previously recycled.
         *
         * @param recycler Recycler to scrap views into
         */
        public void detachAndScrapAttachedViews(Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                final View v = getChildAt(i);
                scrapOrRecycleView(recycler, i, v);
            }
        }

遍历RecyclerView的所有child,并执行scrapOrRecycleView()方法。

        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); // ③
            }
        }

由于使用notifyItemChanged()做局部刷新,所以viewHolder.isInvalid()为false,因此会执行①②③,重点关注下①和②。
看下detachViewAt(int index)方法及其内部调用到的方法。

        // 以下代码在RecyclerView.LayoutManager中
        public void detachViewAt(int index) {
            detachViewInternal(index, getChildAt(index));
        }

        private void detachViewInternal(int index, View view) {
            mChildHelper.detachViewFromParent(index);
        }
    // 以下代码在ChildHelper中
    void detachViewFromParent(int index) {
        final int offset = getOffset(index);
        // 更新ChildHelper,此处不需关注
        mBucket.remove(offset); // ① 
        mCallback.detachViewFromParent(offset);
    }
    
            // 以下代码在RecyclerView的initAdapterManager()中创建的Adapter.Callback中
            @Override
            public void detachViewFromParent(int offset) {
                final View view = getChildAt(offset);
                if (view != null) {
                    final ViewHolder vh = getChildViewHolderInt(view);
                    if (vh != null) {
                        // ViewHolder添加FLAG_TMP_DETACHED
                        vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); // ② 
                    }
                }
                // 直接调用父类ViewGroup的detachViewFromParent()方法
                RecyclerView.this.detachViewFromParent(offset); // ③ 
            }
    // 以下代码在ViewGroup中
    /**
     * Detaches a view from its parent. Detaching a view should be followed
     * either by a call to
     * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
     * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
     * temporary; reattachment or removal should happen within the same drawing cycle as
     * detachment. When a view is detached, its parent is null and cannot be retrieved by a
     * call to {@link #getChildAt(int)}.
     */
    protected void detachViewFromParent(int index) {
        removeFromArray(index);
    }

可以看到detachViewAt(int index)方法,一共做了三件事,需要关注的是ViewHolder添加FLAG_TMP_DETACHED这个flag,另外调用了ViewGroup的detachViewFromParent()方法,暂时将View从ViewGroup的mChildren数组中清除,并将View的parent置为null。
回到scrapOrRecycleView()方法,看下② Recycler.scrapView()方法及其调用的方法。

        // 以下代码在RecyclerView的Recycler类中
        /**
         * Mark an attached view as scrap.
         *
         * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
         * for rebinding and reuse. Requests for a view for a given position may return a
         * reused or rebound scrap view instance.</p>
         *
         * @param view View to scrap
         */
        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);
            }
        }
    // 以下代码在RecyclerView中    
    boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
        // RecyclerView中的mItemAnimator默认为DefaultItemAnimator。
        return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
                viewHolder.getUnmodifiedPayloads()); 
    }
    // 以下代码在DefaultItemAnimator中
    /**
     * {@inheritDoc}
     * <p>
     * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
     * When this is the case:
     * <ul>
     * <li>If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both
     * ViewHolder arguments will be the same instance.
     * </li>
     * <li>
     * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)},
     * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and
     * run a move animation instead.
     * </li>
     * </ul>
     */
    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
            @NonNull List<Object> payloads) {
        return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
    }
    // 以下代码在DefaultItemAnimator的直接父类SimpleItemAnimator中
    /**
     * {@inheritDoc}
     *
     * @return True if change animations are not supported or the ViewHolder is invalid,
     * false otherwise.
     *
     * @see #setSupportsChangeAnimations(boolean)
     */
    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
        return !mSupportsChangeAnimations || viewHolder.isInvalid();
    }
        // 以下代码在ReyclerView.ViewHolder中
        void setScrapContainer(Recycler recycler, boolean isChangeScrap) {
            mScrapContainer = recycler;
            mInChangeScrap = isChangeScrap;
        }

从SimpleItemAnimator的canReuseUpdatedViewHolder()方法看起,由于mSupportsChangeAnimations默认值为true,除非调用setSupportsChangeAnimations将其设置为false;同时在notifyItemChanged()的情况下,viewHolder.isInvalid()为false。所以SimpleItemAnimator的canReuseUpdatedViewHolder方法返回false。那么对于DefaultItemAnimator的canReuseUpdatedViewHolder()方法,返回值就完全取决于!payloads.isEmpty()的值,如果payloads为空,则返回false;如果payloads不为空,则返回true。回到scrapView(View)方法中,对于要update的ViewHolder(ViewHolder.isUpdated()为true),如果payloads为空,ViewHolder会被添加到mChangedScrap列表中,并将ViewHolder的mInChangeScrap变量设置为true,否则会添加到mAttachedScrap列表中并将ViewHolder的mInChangeScrap变量设置为false。
回到LinearLayoutManager中的onLayoutChildren()方法中,看下fill()方法。fill方法中不停调用layoutChunk()方法添加View,直到屏幕占满。

    // 以下代码在LinearLayoutManager中
    /**
     * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
     * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
     * and with little change, can be made publicly available as a helper class.
     *
     * @param recycler        Current recycler that is attached to RecyclerView
     * @param layoutState     Configuration on how we should fill out the available space.
     * @param state           Context passed by the RecyclerView to control scroll steps.
     * @param stopOnFocusable If true, filling stops in the first focusable new child
     * @return Number of pixels that it added. Useful for scroll functions.
     */
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (layoutChunkResult.mFinished) {
                break;
            }
        }
        return start - layoutState.mAvailable;
    }
    
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        if (view == null) {
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        result.mFocusable = view.hasFocusable();
    }

注意看下layoutState的next()方法,这里有很多熟悉的方法,比如createViewHolder,bindViewHolder。

        // 以下代码在LinearLayoutManager.LayoutState中
        /**
         * Gets the view for the next element that we should layout.
         * Also updates current item index to the next item, based on {@link #mItemDirection}
         *
         * @return The next element that we should layout.
         */
        View next(RecyclerView.Recycler recycler) {
            // 此处mScrapList为null
            if (mScrapList != null) { 
                return nextViewFromScrapList();
            }
            // 从Recycler中获取View,Recycler中有mAttachedScrap、mChangedScrap、mCachedViews三个List,还有ViewCacheExtension,RecyclerViewPool
            final View view = recycler.getViewForPosition(mCurrentPosition); 
            mCurrentPosition += mItemDirection;
            return view;
        }
        // 以下代码在RecylcerView.Recycler中
        /**
         * Obtain a view initialized for the given position.
         *
         * This method should be used by {@link LayoutManager} implementations to obtain
         * views to represent data from an {@link Adapter}.
         * <p>
         * The Recycler may reuse a scrap or detached view from a shared pool if one is
         * available for the correct view type. If the adapter has not indicated that the
         * data at the given position has changed, the Recycler will attempt to hand back
         * a scrap view that was previously initialized for that data without rebinding.
         *
         * @param position Position to obtain a view for
         * @return A view representing the data at <code>position</code> from <code>adapter</code>
         */
        public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }

        View getViewForPosition(int position, boolean dryRun) {
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }
        /**
         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
         * cache, the RecycledViewPool, or creating it directly.
         * <p>
         * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return
         * rather than constructing or binding a ViewHolder if it doesn't think it has time.
         * If a ViewHolder must be constructed and not enough time remains, null is returned. If a
         * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is
         * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.
         *
         * @param position Position of ViewHolder to be returned.
         * @param dryRun True if the ViewHolder should not be removed from scrap/cache/
         * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
         *                   complete. If FOREVER_NS is passed, this method will not fail to
         *                   create/bind the holder if needed.
         *
         * @return ViewHolder for requested position
         */
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) { 
            // 还记得dispatchLayoutStep1中的mState.mInPreLayout = mState.mRunPredictiveAnimations;吗?所以在dispatchLayoutStep1中会先从mChangedScrap中找viewHolder,可以回顾下scrapView()方法。
                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) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            // ...省略后续
            boolean bound = false;
            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);
                // 主要调用了mAdapter.bindViewHolder(holder, offsetPosition);
                // 进而调用了onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());和holder.clearPayload();
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); // ⑦
            }
            // ...省略后续
            return holder;
        }
        
        ViewHolder getChangedScrapViewForPosition(int position) {
            // If pre-layout, check the changed scrap for an exact match.
            final int changedScrapSize;
            if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
                return null;
            }
            // find by position
            for (int i = 0; i < changedScrapSize; i++) {
                final ViewHolder holder = mChangedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
            return null;
        }
        /**
         * Returns a view for the position either from attach scrap, hidden children, or cache.
         *
         * @param position Item position
         * @param dryRun  Does a dry run, finds the ViewHolder but does not remove
         * @return a ViewHolder that can be re-used for this position.
         */
        ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();

            // Try first for an exact, non-invalid match from scrap.
            for (int i = 0; i < scrapCount; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                        && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }

            if (!dryRun) {
                View view = mChildHelper.findHiddenNonRemovedView(position);
                if (view != null) {
                    // This View is good to be used. We just need to unhide, detach and move to the
                    // scrap list.
                    final ViewHolder vh = getChildViewHolderInt(view);
                    mChildHelper.unhide(view);
                    int layoutIndex = mChildHelper.indexOfChild(view);
                    if (layoutIndex == RecyclerView.NO_POSITION) {
                        throw new IllegalStateException("layout index should not be -1 after "
                                + "unhiding a view:" + vh + exceptionLabel());
                    }
                    mChildHelper.detachViewFromParent(layoutIndex);
                    scrapView(view);
                    vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                            | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    return vh;
                }
            }

            // Search in our first-level recycled view cache.
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final ViewHolder holder = mCachedViews.get(i);
                // invalid view holders may be in cache if adapter has stable ids as they can be
                // retrieved via getScrapOrCachedViewForId
                if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    return holder;
                }
            }
            return null;
        }

分析下layoutState的next()方法:从3.3.2可知,mState.isPreLayout()为true,所以会①先从mChangedScrap列表中查找ViewHolder,如果未找到,则②会从mAttachedScrap列表中查找。回顾下scrapView()方法,RecyclerView中View所在的viewHolder不是存进mChangedScrap就是存进mAttachedScrap了。所以,对于notifyItemChanged()这种情况,在pre layout阶段,经过这①②这两步,holder的值就不为空了,由于mState.isPreLayout()为true,且holder.isBound()为true,所以不会执行bindViewHolder操作。
回到layoutChunk()方法,当next()获取到view后,会执行RecyclerView的addView()方法。

        // 
        /**
         * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
         * use this method to add views obtained from a {@link Recycler} using
         * {@link Recycler#getViewForPosition(int)}.
         *
         * @param child View to add
         */
        public void addView(View child) {
            addView(child, -1);
        }

        /**
         * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
         * use this method to add views obtained from a {@link Recycler} using
         * {@link Recycler#getViewForPosition(int)}.
         *
         * @param child View to add
         * @param index Index to add child at
         */
        public void addView(View child, int index) {
            addViewInt(child, index, false);
        }

        private void addViewInt(View child, int index, boolean disappearing) {
            final ViewHolder holder = getChildViewHolderInt(child);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (holder.wasReturnedFromScrap() || holder.isScrap()) {
                if (holder.isScrap()) {
                    holder.unScrap();
                } else {
                    holder.clearReturnedFromScrapFlag();
                }
                mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
            } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
                // ensure in correct position
                int currentIndex = mChildHelper.indexOfChild(child);
                if (index == -1) {
                    index = mChildHelper.getChildCount();
                }
                if (currentIndex == -1) {
                    throw new IllegalStateException("Added View has RecyclerView as parent but"
                            + " view is not a real child. Unfiltered index:"
                            + mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel());
                }
                if (currentIndex != index) {
                    mRecyclerView.mLayout.moveView(currentIndex, index);
                }
            } else {
                mChildHelper.addView(child, index, false);
                lp.mInsetsDirty = true;
                if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
                    mSmoothScroller.onChildAttachedToWindow(child);
                }
            }
            if (lp.mPendingInvalidate) {
                holder.itemView.invalidate();
                lp.mPendingInvalidate = false;
            }
        }

对于此种情况,执行了如下代码,跟scrapOrRecycleView()方法其实非常对称。

holder.unScrap();
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);

当所有RecyclerView添加完View,dispatchLayoutStep1()方法就执行完了,之后就开始执行dispatchLayoutStep2()方法。

3.4 dispatchLayoutStep2()
    /**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
        mAdapterHelper.consumeUpdatesInOnePass();   // ①
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState); // ②

        mState.mStructureChanged = false;
        
        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
    }

dispatchLayoutStep2()是真正进行布局的方法。3.0的前提下,①中什么都没做,就不分析了。重点是3.3.3中花了很长代码介绍的onLayoutChildren()方法,现在再次遇到就相对容易看了。dispatchLayoutStep2()中的onLayoutChildren与3.3.3中pre layout的不同之处,在于mState.mInPreLayout被设置为false了,造成的区别在tryGetViewHolderForPositionByDeadline()中。

        /**
         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
         * cache, the RecycledViewPool, or creating it directly.
         * <p>
         * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return
         * rather than constructing or binding a ViewHolder if it doesn't think it has time.
         * If a ViewHolder must be constructed and not enough time remains, null is returned. If a
         * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is
         * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.
         *
         * @param position Position of ViewHolder to be returned.
         * @param dryRun True if the ViewHolder should not be removed from scrap/cache/
         * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
         *                   complete. If FOREVER_NS is passed, this method will not fail to
         *                   create/bind the holder if needed.
         *
         * @return ViewHolder for requested position
         */
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            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) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount() + exceptionLabel());
                }

                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;
                    }
                }
                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) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder"
                                    + exceptionLabel());
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view." + exceptionLabel());
                        }
                    }
                }
                if (holder == null) { // fallback to pool
                    holder = getRecycledViewPool().getRecycledView(type); // ⑤
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    // 看到了Adapter的createViewHolder方法,主要是调用了onCreateViewHolder方法
                    // final VH holder = onCreateViewHolder(parent, viewType);
                    holder = mAdapter.createViewHolder(RecyclerView.this, type); // ⑥
                    if (ALLOW_THREAD_GAP_WORK) {
                        // only bother finding nested RV if prefetching
                        RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                        if (innerView != null) {
                            holder.mNestedRecyclerView = new WeakReference<>(innerView);
                        }
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            boolean bound = false;
            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);
                // 主要调用了mAdapter.bindViewHolder(holder, offsetPosition);
                // 进而调用了onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());和holder.clearPayload();
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); // ⑦
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder; // ⑧
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;

            return holder;
        }

由于mState.isPreLayout()为false,所以步骤①不会执行,也就是不会从mChangedScrap列表中查找,但使用notifyItemChanged(int position)方法进行刷新的ViewHolder,在scrapView()方法中被存入了mChangedScrap列表中,这就导致步骤②之后,holder 仍为null。之后的流程就是从缓存(cacheExtension、recyclerPool)中查找,如果查找到相同类型的ViewHolder,则从缓存中取出复用;如果没有相同类型的ViewHolder,就只能通过onCreateViewHolder来创建新的viewHolder。但如果对于notifyItemChanged(int position, Object payload)方式进行刷新的ViewHolder,在scrapView()方法中被存入了mAttachedScrap列表中,经过步骤②后,就取得了之前的holder。这两种情况的holder,在步骤⑦处,由于mState.isPreLayout()为false,且holder.needsUpdate() 为true,所以会调用onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());来更新数据,回顾文章第二部分对onBindViewHolder(holder, position, payloads);的实现,可以看到此时喜欢的状态就更新了。
在3.0的前提下,dispatchLayoutStep2()中onLayoutChildren()其他地方都与pre layout流程中onLayoutChildren()无区别。

3.5 dispatchLayoutStep3()
    /**
     * The final step of the layout where we save the information about views for animations,
     * trigger animations and do any necessary cleanup.
     */
    private void dispatchLayoutStep3() {
        mState.mLayoutStep = State.STEP_START;
        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));
                if (holder.shouldIgnore()) {
                    continue;
                }
                long key = getChangedHolderKey(holder);
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); // ①
                if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                    // run a change animation

                    // If an Item is CHANGED but the updated version is disappearing, it creates
                    // a conflicting case.
                    // Since a view that is marked as disappearing is likely to be going out of
                    // bounds, we run a change animation. Both views will be cleaned automatically
                    // once their animations finish.
                    // On the other hand, if it is the same view holder instance, we run a
                    // disappearing animation instead because we are not going to rebind the updated
                    // VH unless it is enforced by the layout manager.
                    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);
                }
            }

            // Step 4: Process view info lists and trigger animations
            mViewInfoStore.process(mViewInfoProcessCallback);
        }
        // ...省略恢复初始设置的代码
    }

dispatchLayoutStep3()对ChildHelper中view进行遍历,首先从view中获取到holder,再根据holder的key,从ViewStoreInfo获取3.3 dispatchLayoutStep1()步骤③中存入的oldChangeViewHolder,由于oldDisappearing为false且preInfo不为null,所以会调用animateChange(oldChangeViewHolder, holder, preInfo, postInfo, oldDisappearing, newDisappearing)方法。另外,需要注意的是,根据3.4的分析,如果是在notifyItemChanged(int position)调用导致的dispatchLayoutStep3()中,oldChangeViewHolder与holder不同;如果是在notifyItemChanged(int position, Object payload)调用导致的dispatchLayoutStep3()中,oldChangeViewHolder与holder相同。
接着看下animateChange()及其调用的相应方法。

    // 以下代码在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;
            // old holder should disappear after animation ends
            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) {
            // Don't know how to run change animations when the same view holder is re-used.
            // run a move animation to handle position changes.
            return animateMove(oldHolder, fromX, fromY, toX, toY);
        }
        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;
        if (deltaX == 0 && deltaY == 0) {
            dispatchMoveFinished(holder);
            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;
    }
    
    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;
        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();
        }
        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);
                            dispatchChangeFinished(changeInfo.newHolder, false);
                            mChangeAnimations.remove(changeInfo.newHolder);
                            dispatchFinishedWhenDone();
                        }
                    }).start();
        }
    }

可以看到如果oldHolder与newHolder相同,执行animateMove()方法,由于oldHolder与newHolder的位置相同,所以直接return了。而oldHolder与newHolder不同时,执行animateChange()方法,将newHolder 的view的alpha值设置为0。在动画真正运行,即执行animateChangeImpl()方法时,newHolder的view会动画显示(alpha从0到1),oldHolder的view会动画消失(alpha从1到0),这样实现了CrossFade的效果,也就是出现了闪烁。

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
RecyclerView局部刷新闪烁是指在使用RecyclerView进行局部刷新时,界面出现明显的闪烁现象。这种现象通常是由于RecyclerView的Item刷新过程中引起的,下面是一些可能导致RecyclerView局部刷新闪烁的原因和解决方法。 1. 数据源变化频繁:当RecyclerView的数据源频繁发生变化时,局部刷新可能会导致闪烁。解决办法可以是尽量减少数据源的变化,例如使用DiffUtil工具类来优化数据比较,只更新真正变化的Item。 2. ViewHolder的重用问题:如果RecyclerView的Item中使用了动画效果,而在进行局部刷新时未正确处理ViewHolder的重用问题,可能会导致动画重复播放,从而产生闪烁。解决办法是在局部刷新的时候,清除或暂停动画效果,并正确处理ViewHolder的重用逻辑。 3. 刷新时的绘制问题:默认情况下,RecyclerView进行Item的局部刷新时,会重新绘制整个Item View,这可能会导致闪烁。解决办法是通过重写Item View的onDraw()方法,在局部刷新时只绘制变化的部分,而不是整个Item View。 4. 使用过多的动画效果:如果RecyclerView中的Item View使用了过多的动画效果,例如淡入淡出、旋转等,这些动画效果在局部刷新时会导致闪烁。解决办法是优化动画效果的使用,尽量减少动画效果的数量和复杂度。 总之,解决RecyclerView局部刷新闪烁问题的方法包括优化数据源的变化、正确处理ViewHolder的重用逻辑、优化绘制和动画效果的使用等。通过这些措施,可以有效减少RecyclerView局部刷新闪烁的现象,提升用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值