RecyclerView源码分析(四)--动画流程

看完本文你大概需要 8.3分 的毅力

相关系列文章

RecyclerView源码分析(一)--整体设计
RecyclerView源码分析(二)--测量流程
RecyclerView源码分析(三)--布局流程

上一篇文章讲了RecyclerView的布局流程,发现里面大多数内容都是和动画相关的。那么这边文章就先讲RecyclerView中,数据改变发出通知到播放动画的一系列流程。

RecyclerView的动画流程

对于RecyclerView的动画流程,是一个非常的长的流程,那么我们先把大的东西分部分来看,会轻松一点。首先,回想一下,我们通常在RecyclerView中数据改变的时候,调用什么函数来使其播放动画的。通常我们都是调用Adapter的以下函数。

notifyDataSetChanged()

notifyItemChanged(int position)

notifyItemRangeChanged(int positionStart, int itemCount)

notifyItemRangeChanged(int positionStart, int itemCount, Object payload)

notifyItemInserted(int position)

notifyItemMoved(int fromPosition, int toPosition)

notifyItemRangeInserted(int positionStart, int itemCount)

notifyItemRemoved(int position)

notifyItemRangeRemoved(int positionStart, int itemCount)

下面我们把这些函数统称为Adapter的通知函数

那么我们就从这里出发开始RecyclerView的动画流程。而在上一篇布局流程源码分析中,在最后总结时提到的。

RecyclerView的数据改变的动画是在布局过程的第三步中统一触发的。并不是一调用notify之后立即触发。

可以看出动画的一部分的处理是在布局过程中完成的,那么我们可以把动画流程分为两个部分。布局过程到动画触发的部分,和Adapter发出通知到布局之前的部分。

下面我们以插入作为例子分析,其他过程大致类似。

Adapter通知到布局之前的处理

首先,盗用该系列文章中的第一篇文章介绍Adapter通知流程的图:

Adapter的通知过程是一个观察者模式。结合源码:

public static abstract class Adapter<VH extends ViewHolder> {

    private final AdapterDataObservable mObservable = new AdapterDataObservable();
    
    public void registerAdapterDataObserver(AdapterDataObserver observer) {
        mObservable.registerObserver(observer);
    }
    
    public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
        mObservable.unregisterObserver(observer);
    }
    
    public final void notifyItemInserted(int position) {
        mObservable.notifyItemRangeInserted(position, 1);
    }
}

我们可以看到Adapter中包含一个AdapterDataObservable的对象mObservable,这个是一个可观察者,在可观察者中可以注册一系列的观察者AdapterDataObserver。在我们调用的notify函数的时候,就是可观察者发出通知,这时已经注册的观察者都可以收到这个通知,然后依次进行处理。

那么我们看一下注册观察者的地方。

注册观察者的地方就是在RecyclerView的这个函数中。这个是setAdapter方法最终调用的地方。它主要做了:

  1. 如果之前存在Adapter,先移除原来的,注销观察者,和从RecyclerView Detached。
  2. 然后根据参数,决定是否清除原来的ViewHolder
  3. 然后重置AdapterHelper,并更新Adapter,注册观察者。
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        if (mItemAnimator != null) {
            mItemAnimator.endAnimations();
        }
        if (mLayout != null) {
            mLayout.removeAndRecycleAllViews(mRecycler);
            mLayout.removeAndRecycleScrapInt(mRecycler);
        }
        mRecycler.clear();
    }
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    markKnownViewsInvalid();
}

从这里我们可以看出,mObserver这个成员变量就是注册的观察者,那么我们去看看这个成员变量的内容。

该成员变量是一个RecyclerViewDataObserver的实例,那么RecyclerViewDataObserver实现了AdapterDataObserver中的方法。其中onItemRangeInserted(int positionStart, int itemCount)就是观察者接受到有数据插入通知的方法。那么我们来分析这个方法。看注释。

private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

private class RecyclerViewDataObserver extends AdapterDataObserver {
    
    ……
    mPostUpdatesOnAnimation = version >= 16;

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        // 1) 断言不在布局或者滚动过程中,其实就是如果在布局或者滚动过程中,则不会执
        // 行下面的内容
        assertNotInLayoutOrScroll(null);
        // 2) 这里小型,不要小看if括号中的内容,这是关键。我们去看看这个方法的实现。
        // 见下面注释 3),在 3) 返回true之后执行triggerUpdateProcessor方法,
        // triggerUpdateProcessor方法分析请看注释 4)。
        if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }
}

AdapterHelper中onItemRangeInserted函数即相关内容,请看注释 3)。

class AdapterHelper implements OpReorderer.Callback {

    // 一个待处理更新操作的列表,该列表中存放所有等待处理的操作信息。
    final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();

    // 3) 该方法将插入操作的信息存储到一个UpdateOp中,并添加到待处理更新操作列表中,
    // 如果操作列表中的值是1,就返回真表示需要处理操作,等于1的判断避免重复触发处理操作。
    // obtainUpdateOp内部是通过池来得到一个UpdateOp对象。那么下面回去看我们注释 4)。
    boolean onItemRangeInserted(int positionStart, int itemCount) {
        if (itemCount < 1) {
            return false;
        }
        mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
        mExistingUpdateTypes |= UpdateOp.ADD;
        return mPendingUpdates.size() == 1;
    }
}
// 4) 触发更新处理操作,分为两种情况,在 版本大于16 且 已经Attach 并且 设置了大小固定 的情况下,
// 进行mUpdateChildViewsRunnable中的操作。否则请求布局。
void triggerUpdateProcessor() {
    if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}
    
// 5) 其中核心代码是consumePendingUpdateOperations()那么继续往下看。
private final Runnable mUpdateChildViewsRunnable = new Runnable() {
    public void run() {
        ……
        consumePendingUpdateOperations();
    }
};

private void consumePendingUpdateOperations() {
    ……
    if (mAdapterHelper.hasAnyUpdateTypes(UpdateOp.UPDATE) && !mAdapterHelper
            .hasAnyUpdateTypes(UpdateOp.ADD | UpdateOp.REMOVE | UpdateOp.MOVE)) {
        // 6) 如果只有更新类型的操作(这里指内容的更新,不影响View位置的改变)的情况下,
        // 先进行预处理,然后在没有View更新的情况下消耗延迟的更新操作,否则调用
        // dispatchLayout方法对RecyclerView中的View重新布局。那么接下来分析
        // preProcess()方法。
        mAdapterHelper.preProcess();
        if (!mLayoutRequestEaten) {
            if (hasUpdatedView()) {
                dispatchLayout();
            } else {
                mAdapterHelper.consumePostponedUpdates();
            }
        }
        resumeRequestLayout(true);
    } else if (mAdapterHelper.hasPendingUpdates()) {
        // 7) 在既有更新操作又有添加或者删除或者移动中任意一个的情况下,调用
        // dispatchLayout方法对RecyclerView中的View重新布局
        dispatchLayout();
    }
}

AdapterHelper preProcess方法源码:

// 8) 预处理做了以下几件事情,<1> 先将待处理操作重排。<2> 应用所有操作 <3> 清空待处理操作列表,
// 以ADD为例分析流程。
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();
}

// 9) 直接看postponeAndUpdateViewHolders
private void applyAdd(UpdateOp op) {
    postponeAndUpdateViewHolders(op);
}

// 10) 先将操作添加到推迟的操作列表中。然后将操作的内容交给回调处理。
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);
    }
}

该回调是在RecyclerView初始化的时候,初始化AdapterHelper的时候设置进来的。我们先只研究offsetPositionsForAdd这个回调分析流程。

// 11) 直接看offsetPositionRecordsForInsert
@Override
public void offsetPositionsForAdd(int positionStart, int itemCount) {
    offsetPositionRecordsForInsert(positionStart, itemCount);
    mItemsAddedOrRemoved = true;
}
// 12) 该方法主要是便利所有的ViewHolder,然后把在插入位置之后的ViewHolder的位置
// 向后移动插入的个数,最后在对Recycler中缓存的ViewHolder做同样的操作,最后申请重新布局。
void offsetPositionRecordsForInsert(int positionStart, int 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() && holder.mPosition >= positionStart) {
            holder.offsetPosition(itemCount, false);
            mState.mStructureChanged = true;
        }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
}

布局流程到触发动画的过程

根据上面的分析,如果我们没有设置mHasFixedSize=true,那么我们最早在 4) 步就会requestLayout,从而使View重新Layout,就是在那么现在我们再来看布局流程:

首先是step1:

private void dispatchLayoutStep1() {
    ……
    if (mState.mRunSimpleAnimations) {
        // 遍历所有显示着的的ViewHolder,找到那些没有被移除的ViewHolder,储存在mViewInfoStore中,并找到那些只有内容更新的ViewHolder储存在mViewInfoStore中,前后存储的容器不同,然后准备执行之前的布局。mItemAnimator的recordPreLayoutInformation方法就是将ViewHolder的边界信息记录在ItemHolderInfo中。
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                continue;
            }
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
                long key = getChangedHolderKey(holder);
                mViewInfoStore.addToOldChangeHolders(key, holder);
            }
        }
    }
    if (mState.mRunPredictiveAnimations) {
        // 下面的内容是需要在布局结束之后运行动画的情况下执行的。主要做的事情就是
        // 执行上一布局操作,上一布局操作其实就是先以上一次的状态执行一边LayoutManager
        // 的onLayoutChildren方法,其实RecyclerView的布局策略就是在
        // LayoutManager的onLayoutChildren方法中。执行一次它就获得了所有
        // ViewHolder的边界信息。只不过,这次获得的是之前状态下的ViewHolder的
        // 边界信息。不过这个应该是要在LayoutManager中,根据state的isPreLayout
        // 的返回值,选择使用新的还是旧的position。但我在系统给的几个LayoutManager中
        // 都没有看到。
        saveOldPositions();
        final boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = didStructureChange;
        // 遍历ViewHolder,判断动画标志,存储到mViewInfoStore
        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);
                }
            }
        }
        ……
    } 
    ……
}

这里我觉得有必要讲一个mViewInfoStore,mViewInfoStore是一个ViewInfoStore。来看一下这个类:这个类是用来追踪View所要做的动画的。其中有一个内部类InfoRecord,该类用来存储ViewHolder前后的信息,以及ViewHolder状态的flag。

其中有两个比较重要的成员变量,mLayoutHolderMap和mOldChangedHolders,分别用来存储我们的布局改变的ViewHolder对应的动画信息和内容改变的ViewHolder。

ViewInfoStore通过addToPostLayout,addToPreLayout……等方法添加ViewHolder到mLayoutHolderMap中,并且为其InfoRecord设置对应的flag。

在layout的过程中就是分析View的各种状态,然后使用对应的方法添加到ViewInfoStore中

ViewInfoStore中还有一个非常重要的方法,process,该方法会处理所有的mLayoutHolderMap中的值,并根据其flag和前后的信息来判断ViewHolder的动作,并将这个动作反应给ProcessCallback。分别有4种行为:消失,出现,一直存在,为使用。然后交给外面去处理。

class ViewInfoStore {
    final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();

    final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();

    void addToPreLayout(ViewHolder holder, ItemHolderInfo info) { …… }
    
    void addToPostLayout(ViewHolder holder, ItemHolderInfo info) { …… }
    
    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);
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                callback.unused(viewHolder);
            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                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) {
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE) != 0) {
                callback.processDisappeared(viewHolder, record.preInfo, null);
            } else if ((record.flags & FLAG_POST) != 0) {
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_APPEAR) != 0) {
            } 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);
    }

    static class InfoRecord {
        ……
        int flags;
        @Nullable ItemHolderInfo preInfo;
        @Nullable ItemHolderInfo postInfo;
        ……
    }
}

好,简单了解一下ViewInfoStore之后,继续看布局流程。dispatchLayoutStep2中没有对动画的相关处理,直接看第三步。在判断里面,我不去一行一行的讲了,它们就是遍历所有的ViewHolder,然后判断各种情况,最后添加到mViewInfoStore中。遍历结束之后执行mViewInfoStore的process处理所有的在其mLayoutHolderMap中的ViewHolder。

private void dispatchLayoutStep3() {
    if (mState.mRunSimpleAnimations) {
        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()) {
                final boolean oldDisappearing = mViewInfoStore.isDisappearing(
                        oldChangeViewHolder);
                final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                if (oldDisappearing && oldChangeViewHolder == holder) {
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                } else {
                    final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
                            oldChangeViewHolder);
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                    ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                    if (preInfo == null) {
                        handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
                    } else {
                        animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
                                oldDisappearing, newDisappearing);
                    }
                }
            } else {
                mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
        }

        // Step 4: Process view info lists and trigger animations
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    ……
}

之后我们看一下mViewInfoStore的ProcessCallback的实现mViewInfoProcessCallback,这里只拿processAppeared做分析:

private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
    ……
    @Override
    public void processAppeared(ViewHolder viewHolder,
            ItemHolderInfo preInfo, ItemHolderInfo info) {
        animateAppearance(viewHolder, preInfo, info);
    }
    ……
};

然后看一下animateAppearance方法:

private void animateAppearance(@NonNull ViewHolder itemHolder,
        @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    itemHolder.setIsRecyclable(false);
    if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
        postAnimationRunner();
    }
}

该方法中不要忽略if中的内容:mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)
那么进入该方法:看注释。

@Override
public boolean animateAppearance(@NonNull ViewHolder viewHolder,
        @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    // 该方法通过前后的布局信息来判断是移动还是添加。下面我们以添加为例分析
    if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
            || preLayoutInfo.top != postLayoutInfo.top)) {
        return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
                postLayoutInfo.left, postLayoutInfo.top);
    } else {
        return animateAdd(viewHolder);
    }
}

真正实现是在DefaultItenAnimator中:这里做了三件事情,重置holder的动画,设置显示属性,然后添加到mPendingAdditions中,mPendingAdditions是一个存储添加ViewHolder的List,表示待处理的添加动画的ViewHolder。同样在DefaultItenAnimator总也有,移动的,移除的列表。

@Override
public boolean animateAdd(final ViewHolder holder) {
    resetAnimation(holder);
    ViewCompat.setAlpha(holder.itemView, 0);
    mPendingAdditions.add(holder);
    return true;
}

最后返回true,进入if,执行postAnimationRunner方法。

private void postAnimationRunner() {
    if (!mPostedAnimatorRunner && mIsAttached) {
        ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
        mPostedAnimatorRunner = true;
    }
}

尼玛逗我,去看mItemAnimatorRunner,其中调用的ItemAnimator的runPendingAnimations方法。

private Runnable mItemAnimatorRunner = new Runnable() {
    @Override
    public void run() {
        if (mItemAnimator != null) {
            mItemAnimator.runPendingAnimations();
        }
        mPostedAnimatorRunner = false;
    }
};

然后分析runPendingAnimations方法:该方法并不难,按照移除,移动,改变,添加,依次处理之前的待处理列表中的内容。这里还是以添加的做为例子来分析,看注释。

public void runPendingAnimations() {
    boolean removalsPending = !mPendingRemovals.isEmpty();
    boolean movesPending = !mPendingMoves.isEmpty();
    boolean changesPending = !mPendingChanges.isEmpty();
    boolean additionsPending = !mPendingAdditions.isEmpty();
    if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
        return;
    }
    for (ViewHolder holder : mPendingRemovals) {
        ……
    }
    mPendingRemovals.clear();
    if (movesPending) {
        ……
    }
    if (changesPending) {
        ……
    }
    if (additionsPending) {
        final ArrayList<ViewHolder> additions = new ArrayList<>();
        additions.addAll(mPendingAdditions);
        mAdditionsList.add(additions);
        mPendingAdditions.clear();
        // 重要的是这个adder。其中重要的是 animateAddImpl(holder) 方法。那么来分析这个方法。
        Runnable adder = new Runnable() {
            public void run() {
                for (ViewHolder holder : additions) {
                    animateAddImpl(holder);
                }
                additions.clear();
                mAdditionsList.remove(additions);
            }
        };
        if (removalsPending || movesPending || changesPending) {
            long removeDuration = removalsPending ? getRemoveDuration() : 0;
            long moveDuration = movesPending ? getMoveDuration() : 0;
            long changeDuration = changesPending ? getChangeDuration() : 0;
            long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
            View view = additions.get(0).itemView;
            ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
        } else {
            adder.run();
        }
    }
}

这个方法其实就是通过属性动画对ViewHolder中的View做渐变动画。

private void animateAddImpl(final ViewHolder holder) {
    final View view = holder.itemView;
    final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
    mAddAnimations.add(holder);
    animation.alpha(1).setDuration(getAddDuration()).
            setListener(new VpaListenerAdapter() {
                @Override
                public void onAnimationStart(View view) {
                    dispatchAddStarting(holder);
                }
                @Override
                public void onAnimationCancel(View view) {
                    ViewCompat.setAlpha(view, 1);
                }

                @Override
                public void onAnimationEnd(View view) {
                    animation.setListener(null);
                    dispatchAddFinished(holder);
                    mAddAnimations.remove(holder);
                    dispatchFinishedWhenDone();
                }
            }).start();
}

到这里,终于是触发了我们的动画。其它的动作,流程类似,细节不同而已。

总结

其实这个流程具体的细节不是很重要,因为View的状态很多,情况很复杂,我们也没有必要把那些算法都弄清楚。我们知道它的流程即可。那么通过流程我们可以深入理解以下2点:

  1. 如果我们的RecyclerView的高度和宽度不变,那么通过手动执行setHasFixedSize(true),可以在一定程度上减少计算,提高性能。可以在 4) 步的时候绕过requestLayout,只走自身的布局流程。而requestLayout是申请父控件重新布局流程,两者的计算量是不一样的。
  2. 自定义ItemAnimator的时候,如果在animateAppearance,animateDisappearance……方法中直接运行了动画,就返回false,如果是暂存起来,就返回true,然后将真正执行动画的操作放在runPendingAnimations方法中。

花了这么大力气才知道这么点东西。哈哈!fuck the souce code!!

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • RecyclerView包含以下几个重要的组件:1.LayoutManager: 测量和布局子View2.Recy...
    乌龟爱吃肉阅读 3,475评论 4 7
  • 阅读本文你大概需要10分钟 扯淡的导语好像自从RecyclerView这个控件一出现,对这个控件使用方法的文章就层...
    柴泽建_Jack阅读 23,065评论 26 185
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,053评论 0 16
  • 它是我家的狗狗,我非常喜欢它,它一双眼睛总是炯炯有神。 它很擅长卖萌,每次它犯错误的时候总用这种眼神看着我,让我舍...
    angel2005阅读 199评论 0 1