抽丝剥茧RecyclerView - ItemAnimator & Adapter

封面

前言

抽丝剥茧 RecyclerView系列文章的目的在于帮助Android开发者提高对RecyclerView的认知,本文是整个系列的第三篇。

在前面的系列文章中,我们从源码的角度分别研究了:

  • RecyclerView(整体结构)
  • Recycler
  • LayoutManger
  • ItemDecoration(略微了解)

纵观RecyclerView,似乎还剩下ItemAnimatorAdapter,那么本文作为抽丝剥茧RecyclerView系列的最后一篇,自然要将剩下的部分全部分析完毕(文末有往期文章的链接)。

目录

目录

一、RecyclerView中的魔法师 - Adapter

我将Adapter称为RecyclerView中的魔法师,为什么叫它魔法师呢?因为它将数据变成了具体的视图,不过这也是我们平时谈论颇多的适配器模式

Adapter的主要功能是数据转子视图数据管理及通知,所以在了解源码之前,我们还需了解Adpater的相关类:

名称 作用
AdapterDataObservable 数据发生变化的时候实际处理的类
ViewHolder 存入子视图和当前的位置信息,大家应该都很熟悉了~

1. 数据转子视图

在之前的文章《抽丝剥茧RecyclerView - 化整为零》我们介绍Recycler的时候,已经了解到在Recycler如果没有缓存ViewHolder,会调用Adapter#onCreateViewHolder创建一个ViewHolder,我们平常在该方法的实现中,通常会:

View root = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.xxx,viewGroup,false);
return new ViewHolder(root);

以这样的方式创建子视图,创建完的子视图会交给ViewHolder管理,存储在ViewHolder中的itemView,接着Recycler会调用Adapter#onBindViewHolder实现将数据展示在控件中,不过这两个方法都是由控件的使用者实现。

2. 数据管理

每次数据发生变化的时候,我们都需要调用Adapter#notifyxxx通知RecyclerView数据集发生了变化。这次我们以删除为例来分析源码。

2.1 设置适配器

设置适配器的代码是RecyclerView#setAdapter

public void setAdapter(Adapter adapter) {
    // ...
    // 重点方法
    setAdapterInternal(adapter, false, true);
    // ...
}

private void setAdapterInternal(Adapter adapter, Boolean compatibleWithPrevious,
            Boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        // 旧的适配器解除注册
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    // ...
    final Adapter oldAdapter = mAdapter;
    // 对新的适配器检测数据监听
    mAdapter = adapter;
    if (adapter != null) {
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    // ...
}

该代码主要作用有两点:

  • 旧的适配器取消注册
  • 注册新的适配器中的数据变化的通知对象

数据变化通知对象是这个mObserver,来看看这个mObserver是什么:

private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

// 数据发生变化的回调接口
// 通知到RecyclerView中
// RecyclerViewDataObserver继承自AdapterDataObserver
public abstract static class AdapterDataObserver {
    public void onChanged() {
        // Do nothing
    }
    public void onItemRangeChanged(int positionStart, int itemCount) {
        // do nothing
    }
    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);
    }
    public void onItemRangeInserted(int positionStart, int itemCount) {
        // do nothing
    }
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        // do nothing
    }
    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        // do nothing
    }
}

而这个RecyclerViewDataObserver则是继承自AdapterDataObserver抽象类,具体的实现细节我们后面再讨论。

2.2 数据删除

使用场景是这样的:


RecyclerView删除动画.gif

点击界面中的一个删除按钮,删除数据列表中的第一个数据,然后使用适配器通知数据中已经删除:

btnDeleteOne.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        List<String> strings = mAdapter.getValues();
        if(strings.size() == 0)
        return;
        
        // 移除第一个数据
        strings.remove(0);
        // 适配器通知删除
        mAdapter.notifyItemRemoved(0);
}
2.3 适配器通知

这里有必要说明一下:AdapterRecyclerViewDataObserver都是RecyclerView的内部类,所以它们可以直接使用RecyclerView内部的资源。

RecyclerView中的数据删除的时候,我们调用了Adapter#notifyRemoved方法:

public final void notifyItemRemoved(int position) {
    mObservable.notifyItemRangeRemoved(position, 1);
}

发现删除的处理交给了上面介绍的mObservable,我们来看一下RecyclerViewDataObserver#notifyItemRemoved具体实现:

private class RecyclerViewDataObserver extends AdapterDataObserver {
    //... 省略一些方法

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    // 刷新界面或者直接进行动画
    // 删除这里是调用的刷新界面
    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
}

RecyclerViewDataObserver的通知删除方法中,它又把删除的处理交给了AdapterHelper,调用了AdapterHelper#onItemRangeRemoved

/**
 * @return True if updates should be processed.
 */
Boolean onItemRangeRemoved(int positionStart, int itemCount) {
    if (itemCount < 1) {
        return false;
    }
    // mPendingUpdates是List<UpdateOp>
    // 这里是将一个删除的UpdateOp加入mPendingUpdates中
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
    mExistingUpdateTypes |= UpdateOp.REMOVE;
    return mPendingUpdates.size() == 1;
}

AdapterHelper对自己比较有信心,没有交给别人处理,他在自己的mPendingUpdates中加入一个删除标记的UpdateOp,这个mPendingUpdates有什么作用呢?我们同样在使用的时候介绍。回到RecyclerViewDataObserver中的RecyclerViewDataObserver#notifyItemRemoved,调用完AdapterHelper#onItemRangeRemoved之后,它立马又调用了requestLayout进行界面刷新。

目录.png

2.4 界面绘制流程的一些细节
界面绘制一直是我们之前博客的重点讨论对象,本章我们就数据通知再看一下关于数据通知的细节。

RecyclerView#dispatchLayoutStep1方法中,RecyclerView会调用RecyclerView#processAdapterUpdatesAndSetAnimationFlags处理Adapter中的更新和为动画设置标记,这里我们只看适配器数据更新相关:

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

private Boolean predictiveItemAnimationsEnabled() {
    // RecyclerView设置了默认的mItemAnimator,
    // 以及LinearLayout的supportsPredictiveItemAnimations()为true
    // 该方法返回为true
    return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
}

由于RecyclerView#predictiveItemAnimationsEnabled通常会返回true,那我们跳到AdapterHelper,查看AdapterHelper#preProcess方法:

void preProcess() {
    // ...
    final int count = mPendingUpdates.size();
    for (int i = 0; i < count; i++) {
        UpdateOp op = mPendingUpdates.get(i);
        switch (op.cmd) {
            // ... 添加省略

            case UpdateOp.REMOVE:
                applyRemove(op);
                break;

            // 更新、移动标签省略
        }
    }
    mPendingUpdates.clear();
}

mPendingUpdates是一个ArrayList<UpdateOp>,上述方法就是消费我们在之前添加进mPendingUpdates的删除UpdateOp,在处理删除属性的UpdateOpAdapterHelper#applyRemove方法中又调用AdapterHelper#postponeAndUpdateViewHolders

private void postponeAndUpdateViewHolders(UpdateOp op) {
    mPostponedList.add(op);
    switch (op.cmd) {
        // ... 省略添加、更新、移动

        case UpdateOp.REMOVE:
            mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,op.itemCount);
            break;
        default:
            throw new IllegalArgumentException("Unknown update op type for " + op);
    }
}

真实的处理交给了AdapterHelper中的mCallback,而mCallback的实现同样也在RecyclerView,那我们直接查看mCallback的具体实现:

void initAdapterManager() {
    mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
        // ...省略其他方法
                                       
        // 仅仅展示删除的方法
        @Override
        public void offsetPositionsForRemovingLaidOutOrNewView(
                            int positionStart, int itemCount) {
            offsetPositionRecordsForRemove(positionStart, itemCount, false);
            mItemsAddedOrRemoved = true;
        }
        //... 省略其他方法
    });
}

void offsetPositionRecordsForRemove(int positionStart, int itemCount,
            Boolean applyToPreLayout) {
    final int positionEnd = positionStart + itemCount;
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderint(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
            if (holder.mPosition >= positionEnd) {
                // 更新未删除的ViewHOlder的的位置信息
                holder.offsetPosition(-itemCount, applyToPreLayout);
                mState.mStructureChanged = true;
            } else if (holder.mPosition >= positionStart) {
                // 跟新要删除逇ViewHolder的位置信息
                holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
                                            applyToPreLayout);
                mState.mStructureChanged = true;
            }
        }
    }
    mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
    requestLayout();
}

上述代码的作用主要有两点:

  • 对于要删除的ViewHolder加上删除的flag,更新ViewHolder的位置
  • 对于位置会变化的ViewHolder则更新位置

在数据删除以后,Adapter的作用就是为这些变化的ViewHolder添加删除标签和更新位置信息,后续的处理就交给了LayoutManagerItemAnimator,我们在下面的动画中分析~

二、界面交互的粘合剂 - ItemAnimator

好的动画会让界面的交互很自然,RecyclerView作为一款强大的UI控件,自然也是支持动画的,没错,RecyclerView子视图动画是由ItemAnimator实现的。

上文中的Gif不适合讲解,于是我换了一张聊天图,同样要删除第一条信息:

开始图片

上文中,我们讨论Adapter的结果是它更新了ViewHolder的一些flag,那么这些有了flagViewHolder是如何处理的呢?

在此之前,简单了解一下动画相关类ViewInfoStore

ViewInfoStore

1. 进行预布局

预布局是什么呢?简单来说,RecyclerView进行真实的布局之前,提前进行一次布局,也就是说,LayoutManager#onLayoutChildren方法会执行两次,那么为什么会执行两次呢?我们慢慢分析。

预布局是一个很重要得过程,当有简单的子视图动画发生的时候,它就会被触发,这一点我们得回顾一下RecyclerView#dispatchLayoutStep1方法,直接进入其中的RecyclerView#processAdapterUpdatesAndSetAnimationFlags方法:

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;
    // mFirstLayoutComplete会在第一次布局完成以后设置为true
    mState.mRunSimpleAnimations = mFirstLayoutComplete
                    && mItemAnimator != null
                    && (mDataSetHasChangedAfterLayout
                    || animationTypeSupported
                    || mLayout.mRequestedSimpleAnimations)
                    && (!mDataSetHasChangedAfterLayout
                    || mAdapter.hasStableIds());
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                    && animationTypeSupported
                    && !mDataSetHasChangedAfterLayout
                    && predictiveItemAnimationsEnabled();
}

从上面的代码中,我们可以看出:

  • 第一段注释中表明,简单的动画会触发预布局
  • RecyclerView第一次布局完成以后才有资格触发动画,mFirstLayoutComplete是在第一次布局完成以后设置为true
  • mState.mRunSimpleAnimationstruemState.mRunPredictiveAnimationstrue的充要条件,mState.mRunPredictiveAnimations这个属性很重要,由它决定是否进行预布局

重新回到RecyclerView#dispatchLayoutStep1方法:

private void dispatchLayoutStep1() {
    // ... 
    mViewInfoStore.clear();
    processAdapterUpdatesAndSetAnimationFlags();
    // 重置一些状态
    // ... 省略
    mItemsAddedOrRemoved = mItemsChanged = false;
    // 是否预布局取决于mState.mRunPredictiveAnimations
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    if (mState.mRunSimpleAnimations) {
        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;
            }
            // 记录当前的位置信息 Left、Right、Top、Bottom等
            final ItemHolderInfo animationInfo = mItemAnimator
                                    .recordPreLayoutInformation(mState, holder,
                                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                            holder.getUnmodifiedPayloads());
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            // ... 省略
        }
    }
    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.
        // 大致就是layoutManager会layout每一个子视图,包括后面加入的子视图和删除的子视图,这样以后,layoutManager就很清楚
        // 要执行哪些动画了
        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 (!mViewInfoStore.isInPreLayout(viewHolder)) {
                // 对于新出来的ViewHolder添加标签
                // ... 省略一些方法
                mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                
            }
        }
        // we don't process disappearing list because they may re-appear in post layout pass.
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    // ...
}

除了上面直接进入的方法,还有两个if语句。

第一个if语句:mState.mRunSimpleAnimations为true

这个内容很简单,对预布局前存在的ViewHolder的的位置信息进行记录。

第二个if语句:mState.mRunPredictiveAnimations为true

第二个if语句的内容就复杂多了,首先会进行预布局过程,该过程第一次调用了LayoutManager#onLayoutChildren,关于布局的具体过程,这里我就不讲解了,想要了解的同学可以翻阅我之前的文章:《抽丝剥茧RecyclerView - LayoutManager》

需要指出的是,在添加子视图中,调用了LayoutManager#addViewInt方法:

private void addViewint(View child, int index, Boolean disappearing) {
    // ...
    if (disappearing || holder.isRemoved()) {
        // these views will be hidden at the end of the layout pass.
        mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
    } else {
        // This may look like unnecessary but may happen if layout manager supports
        // predictive layouts and adapter removed then re-added the same item.
        // In this case, added version will be visible in the post layout (because add is
        // deferred) but RV will still bind it to the same View.
        // So if a View re-appears in post layout pass, remove it from disappearing list.
        mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
    }
    // ...
}

该方法的目的是如果是被删除的ViewHolder,它会为ViewInfoStoreViewHolder对应的记录InfoRecord添加已删除的标记,在真实的布局(非预布局)中,被删除的ViewHolder是不会被使用的,所以说,只有预布局才会记录删除动画

预布局完成,界面的样子:
预布局

可以看到,第一次布局完了以后,需要删除的ViewHolder和自动填充的ViewHolder都被加入了RecyclerView,不过,RecyclerView#DispatchLayoutStep1还没结束,它会调用ViewInfoStore会给新加入的ViewHolder添加对应的InfoRecord

完成这个以后,RecyclerView对于要处理哪些动画就了如指掌了,这个也是预布局的意义。

2. 真实布局

同样不讲具体的代码,第二次布局完成以后,界面变成了:

第二次布局

看到上面的图,你可能会有这样的疑问,为什么要删除的子视图没了?说好不讲代码的,打脸,只能甩出一段缓存Recycler的源码了🧐:

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 (... && (mState.mInPreLayout || !holder.isRemoved())) {
            // 在第一级缓存mAttachedScrap中,如果是删除的ViewHolder
            // 预布局是可以使用的,真实布局不可以使用
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    //...
    return null;
}

虽然解决了当前的疑问,你可能还会有另外一个疑问,没有了被删除的子视图,删除动画还怎么执行呢?我们还是先看看接下来的过程吧。

3. 执行动画

之前我们记录了那么多ViewHolder中子视图的信息,现在到了使用的时候了:

private void dispatchLayoutStep3() {
    // ...
    if (mState.mRunSimpleAnimations) {
        // Step 3: Find out where things are now, and process change animations.
        // 找到当前的ViewHolder,执行需要执行的动画
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            ViewHolder holder = getChildViewHolderint(mChildHelper.getChildAt(i));
            long key = getChangedHolderKey(holder);
            final ItemHolderInfo animationInfo = mItemAnimator
                                    .recordPostLayoutInformation(mState, holder);
            ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
            if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                // ...
                if (oldDisappearing && oldChangeViewHolder == holder) {
                    // run disappear animation instead of change
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                } else {
                    // ...
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                    ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                    // ...
                }
            } else {
                mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
        }
        // Step 4: Process view info lists and trigger animations
        // 执行动画
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    // 重置一些跟动画有关的类
    // ...
    mState.mRunSimpleAnimations = false;
    mState.mRunPredictiveAnimations = false;
    // ...
    mViewInfoStore.clear();
}

这个函数的上半部分主要的目的是为了给ViewInfoStore里的ViewHolder相关的InfoRecord添加Post标签,下半部分mViewInfoStore.process(mViewInfoProcessCallback)则是我们的核心功能 - 动画执行,我们重点看一下这个方法:

void process(ProcessCallback callback) {
    for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
        final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
        final InfoRecord record = mLayoutHolderMap.removeAt(index);
        // 根据不同的Flag执行不同的动画
        if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
            callback.unused(viewHolder);
        } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
            // Set as "disappeared" by the LayoutManager (addDisappearingView)
            if (record.preInfo == null) {
                callback.unused(viewHolder);
            } else {
                callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
            }
        } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
            // Appeared in the layout but not in the adapter (e.g. entered the viewport)
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
            // Persistent in both passes. Animate persistence
            callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE) != 0) {
            // Was in pre-layout, never been added to post layout
            callback.processDisappeared(viewHolder, record.preInfo, null);
        } else if ((record.flags & FLAG_POST) != 0) {
            // Was not in pre-layout, been added to post layout
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_APPEAR) != 0) {
            // Scrap view. RecyclerView will handle removing/recycling this.
        } else if (DEBUG) {
            throw new IllegalStateException("record without any reasonable flag combination:/");
        }
        InfoRecord.recycle(record);
    }
}

// 回调接口
interface ProcessCallback {
    void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                    @Nullable ItemHolderInfo postInfo);
    void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
                    ItemHolderInfo postInfo);
    void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                    @NonNull ItemHolderInfo postInfo);
    void unused(ViewHolder holder);
}

ViewInfoStore#process这个关键方法中,遍历mLayoutHolderMap获取ViewHolder绑定的InfoRecord,根据不同flagInfoRecord,回调不同的方法,进而处理不同的动画,回调接口的实现在RecyclerView中:

/**
 * The callback to convert view info diffs into animations.
 */
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
    @Override
    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                            @Nullable ItemHolderInfo postInfo) {
        // 先移除缓存中的ViewHolder
        mRecycler.unscrapView(viewHolder);
        animateDisappearance(viewHolder, info, postInfo);
    }
    
    @Override
    public void processAppeared(ViewHolder viewHolder,
                            ItemHolderInfo preInfo, ItemHolderInfo info) {
        // 出现的动画
        animateAppearance(viewHolder, preInfo, info);
    }
  
    @Override
    public void processPersistent(ViewHolder viewHolder,
                            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        viewHolder.setIsRecyclable(false);
        if (mDataSetHasChangedAfterLayout) {
            // since it was rebound, use change instead as we'll be mapping them from
            // stable ids. If stable ids were false, we would not be running any
            // animations
            if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
                                            postInfo)) {
                postAnimationRunner();
            }
        } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
            postAnimationRunner();
        }
    }
  
    @Override
    public void unused(ViewHolder viewHolder) {
        mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
    }
};

如果你注意到删除方法,你的疑问就更大了,删除的子视图都没了,还执行毛线删除动画?那我就得告诉你了:虽然当前RecyclerView没有需要删除的子视图,但是当前的ViewInfoStoreViewHolder啊,所以在执行删除动画前会将ViewHolder中的子视图重新添加到RecyclerView里面,这里看一下上面的processDisappeared方法调用的RecyclerView#animateDisappearance方法,来看看是不是这样的:

void animateDisappearance(@NonNull ViewHolder holder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
    // 将子视图重新加入到界面
    addAnimatingView(holder);
    holder.setIsRecyclable(false);
    // mItemAnimator执行删除动画
    if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
        postAnimationRunner();
    }
}

/**
 * 将删除的子视图重新添加进界面
 */
private void addAnimatingView(ViewHolder viewHolder) {
    final View view = viewHolder.itemView;
    final Boolean alreadyParented = view.getParent() == this;
    mRecycler.unscrapView(getChildViewHolder(view));
    if (viewHolder.isTmpDetached()) {
        // 重新attach回界面
        mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
    } else if (!alreadyParented) {
        // 添加视图
        mChildHelper.addView(view, true);
    } else {
        mChildHelper.hide(view);
    }
}

一图了解当前界面ViewHolder的状态和需要执行的动画:

执行动画类型

如上面代码块中所看到的,删除子视图的动画实际的执行者是mItemAnimator,其他动画也是如此。

4. DefaultItemAnimator机制

ItemAnimator是一个抽象类,所以一些方法需要具体的类实现,在没有指定具体的ItemAnimator情况下,系统使用了默认的DefaultItemAnimator。一图简单了解DefaultItemAnimator机制:

DefaultItemAnimator

除了DefaultItemAnimator,你还可以自定义一个ItemAnimator,主要实现增、删、更新和移动等一些方法,本文就不再深入了,感兴趣的同学可以自行研究。

DefaultItemAnimator的删除动画中,会对被删除的子视图执行透明度1-0的动画,动画结束后,会删除子视图和回收ViewHolder,位移动画没有放在透明度动画结束后调用,而是使用时间为透明度动画执行时间的延迟,所以看上去就像子视图被删除后下面的子视图才开始网上位移的。

动画执行完毕以后,图片就变成了:


删除完成

以上就是RecyclerView删除部分的AdapterItemAnimator的调用原理,其他方法同学们可以自行分析~

三、总结

总结

还是那句话,没有一遍调试解决不了源码阅读,如果有,那就是两遍😜~,抽丝剥茧RecyclerView源码分析到此就结束了。

如果你想继续了解RecyclcerView:

第一篇:《抽丝剥茧RecyclerView - 化整为零》
第二篇:《抽丝剥茧RecyclerView - LayoutManager》

特别分享篇:

《这么用GridLayoutManager,你可能还真没尝试过》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容