Android - RecyclerView进阶(3)—ItemAnimator分析及自定义

我的CSDN: ListerCi
我的简书: 东方未曦

俗话说,好看的皮囊千篇一律,有趣的灵魂万里挑一。但是对于我们这些俗人来说,肯定是选择好看的皮囊,咱们的用户也是如此。你看看应用市场上那些花枝招展的APP,哪个不是用上了五花八门的动画效果,就算你的内在安全省电性能好,没点儿花招可留不住花心的用户。所以我们今天就来看看怎么实现让用户眼前一亮的动画,当然原理也很重要,因此源码分析必不可少,本文的源码分析主要聚焦于动画是怎么触发的,以及动画是怎么实现的。

一、动画的触发与实现

当Adapter中的数据发生变化时,我们通过notifyItemXXX()等方法通知RecyclerView来改变数据的展示,这个过程必然伴随新的layout()。如果在layout()后直接显示新数据,效果比较僵硬,因此需要通过动画来制造良好的用户体验。
那么,为了实现动画,RecyclerView又额外做了哪些工作呢?抽象上来讲,RecyclerView实现动画的步骤如下。
① 数据发生改变时,保存当前的item信息为preInfo
② 根据新的数据Layout
③ Layout完毕,保存当前的item信息为postInfo
④ 根据preInfo和postInfo判断动画类型并交给ItemAnimator执行
可以发现,前3步保存了执行动画所需要的信息,最后整体交给ItemAnimator来执行动画。前3步涉及到内容较为复杂,我们先从简单的开始分析,来看ItemAnimator是怎么实现动画的。

1.1 动画的实现

由于RecyclerView设计时的低耦合性,ItemAnimator只需要关注怎么执行动画即可,其逻辑并不复杂。RecyclerView为我们实现了DefaultItemAnimator,在不设置动画的情况下默认使用它,其中实现了4个针对item的动画,分别为Remove、Move、Add和Change。以Remove动画为例,当一个item被移出RecyclerView时,DefaultItemAnimator中的animateRemove(holder)方法就会被调用,但是并没有马上开始执行动画,而是将动画添加到了mPendingRemovals中,这是一个待执行的Romove动画List。

    @Override
    public boolean animateRemove(final RecyclerView.ViewHolder holder) {
        resetAnimation(holder);
        mPendingRemovals.add(holder);
        return true;
    }

看一下DefaultItemAnimator的成员变量,原来有4个List分别存储4种动画。

private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();

当RecyclerView要执行动画时,ItemAnimator的runPendingAnimations()方法会被调用,DefaultItemAnimator重写后的方法如下,为了便于阅读,添加了注释并省略了部分代码。

    @Override
    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;
        }
        // 执行Remove动画
        for (RecyclerView.ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder); // 实际执行Remove动画的方法
        }
        mPendingRemovals.clear();
        // Move动画
        if (movesPending) {
            // 注意: Move动画并不是马上执行,会放入一个Runnable
            Runnable mover = new Runnable() {
                @Override
                public void run() {
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                }
            };
            // 如果有Remove动画,就在Remove动画结束之后执行Move动画
            // 如果没有Remove动画就马上执行
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }
        // Change动画,与Move动画一起执行
        if (changesPending) {
            // ......
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                }
            };
            if (removalsPending) {
                RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
        // 最后执行Add动画
        if (additionsPending) {
            // ......
            Runnable adder = new Runnable() {
                @Override
                public void run() {
                    for (RecyclerView.ViewHolder holder : additions) {
                        animateAddImpl(holder);
                    }
                    additions.clear();
                    mAdditionsList.remove(additions);
                }
            };
            // 在Remove、Move、Change动画都完成之后开始执行Add动画
            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();
            }
        }
    }

我们发现动画的执行是有顺序的,Remove动画首先执行,之后Move和Change动画同时开始,等这3个动画全部结束之后开始执行Add动画。以RecyclerView删除一个item为例,动画如下。

gif-Remove动画.gif

我们发现动画有2段,首先是被删除item的Remove动画,等到完全不可见之后,下方的多个item同时执行向上的Move动画。相对的,如果向RecyclerView中添加一个item,会先执行item向下的Move动画,再执行插入item的Add动画。

runPendingAnimations()中真正执行动画的是animateRemoveImpl()这样的方法,来看一下它是怎么实现的,这也是我们今后自定义动画的关键。

    private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimator animation = view.animate();
        mRemoveAnimations.add(holder);
        animation.setDuration(getRemoveDuration()).alpha(0).setListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                        dispatchRemoveStarting(holder);
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        animation.setListener(null);
                        view.setAlpha(1);
                        dispatchRemoveFinished(holder);
                        mRemoveAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }

Remove动画很简单,通过ViewPropertyAnimator实现一个透明度到0的属性动画,不过要记得在动画开始和结束时调用dispatchRemoveStarting()dispatchRemoveFinished()方法。相对的,Add动画就是透明度从0到1的属性动画,而Remove动画就是修改itemView的translation进行移动。
以上3个动画都只涉及1个item,而Change动画会涉及到2个item,DefaultItemAnimator中的实现是让原来的item执行透明度从1到0的动画,让新item执行透明度从0到1的动画。

    void animateChangeImpl(final ChangeInfo changeInfo) {
        final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
        final View view = holder == null ? null : holder.itemView;
        final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
        final View newView = newHolder != null ? newHolder.itemView : null;
        if (view != null) {
            final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
                    getChangeDuration());
            mChangeAnimations.add(changeInfo.oldHolder);
            oldViewAnim.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);
                    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);
                    dispatchChangeFinished(changeInfo.newHolder, false);
                    mChangeAnimations.remove(changeInfo.newHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
    }

以上就是DefaultItemAnimator的基本逻辑,它主要做了两件事:
① 统一安排动画的执行顺序
② 对4种item动画具体实现
自定义ItemAnimator时,我们可以直接使用DefaultItemAnimator的runPendingAnimations()方法来安排动画的执行顺序,只需要修改item的动画即可。

1.2 动画的触发

接下来我们来看动画的触发流程,其关键就在于RecyclerView在执行动画前已经计算出了每个item在动画前后的位置等信息,随后将这些信息传给ItemAnimator统一执行。现在我们从notifyItemRemoved(int position)开始分析动画触发的流程。

    /**
     * 通知注册的观察者,position上的item从数据集中移除了
     * 之前在oldPosition上的item可能会出现在oldPosition - 1的位置上
     */
    public final void notifyItemRemoved(int position) {
        mObservable.notifyItemRangeRemoved(position, 1);
    }

mObservable是一个AdapterDataObservable对象,是RecyclerView数据集的被观察者。mObservable会通知所有的Observer数据发生了改变,RecyclerView有个默认的观察者RecyclerViewDataObserver,来看一下在item被Remove后它做了什么。

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

直接执行了triggerUpdateProcessor()方法。

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

这里有3个判断条件,POST_UPDATES_ON_ANIMATION在SDK>=16时为true;mHasFixedSize默认为false,为true时有优化性能的作用,可以减少requestLayout()方法的调用,这里先不展开,之后性能优化会提到它。
当mHasFixedSize为false时进入else代码块,执行requestLayout()方法,最终进入RecyclerView的onLayout()方法。(PS: mHasFixedSize为true时最终也会执行到onLayout()方法)

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

onLayout()中调用了dispatchLayout()方法,在看它的代码前不妨先看下方法介绍。介绍里第一句就表示,这个方法用于处理由Layout产生的动画,并且将动画分为了5种类型。很显然这个方法就是今天的主角,下面来重点分析。

    /**
     * Wrapper around layoutChildren() that handles animating changes caused by layout.
     * Animations work on the assumption that there are five different kinds of items
     * in play:
     * PERSISTENT: items are visible before and after layout
     * REMOVED: items were visible before layout and were removed by the app
     * ADDED: items did not exist before layout and were added by the app
     * DISAPPEARING: items exist in the data set before/after, but changed from
     * visible to non-visible in the process of layout (they were moved off
     * screen as a side-effect of other changes)
     * APPEARING: items exist in the data set before/after, but changed from
     * non-visible to visible in the process of layout (they were moved on
     * screen as a side-effect of other changes)
     */

dispatchLayout()代码如下,它依次调用dispatchLayoutStep1()dispatchLayoutStep2()dispatchLayoutStep3(),我们来逐个分析。

    void dispatchLayout() {
        // 判空......
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }
dispatchLayoutStep1()

先来讲dispatchLayoutStep1(),它的主要工作有:处理Adapter的数据更新、决定应该运行哪种动画、记录当前所有ItemView的信息、进行预布局pre-layout并保存其信息。简化后的代码如下,只保留了动画相关的部分。

    private void dispatchLayoutStep1() {
        // ......
        if (mState.mRunSimpleAnimations) {
            // 找到所有没有被Remove的Item
            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());
                // 将Item信息保存到mViewInfoStore
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                        && !holder.shouldIgnore() && !holder.isInvalid()) {
                    // ......
                }
            }
        }
        if (mState.mRunPredictiveAnimations) {
            // 开始pre-layout,此时使用的是oldPositions
            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)) {
                    // 这里保存原本在屏幕外的Item的信息,代码省略......
                }
            }
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

首先找到没有被Remove的Item并保存信息,保存时调用的是mViewInfoStore.addToPreLayout(),可以理解为保存的是执行动画前的信息。
如果mState.mRunPredictiveAnimations为true就开始执行pre-layout,pre-layout使用的是Item的oldPosition,它会对所有的Item(包括被Remove的Item)进行布局,并且为动画后显示在屏幕上的Item提供位置。什么意思呢?如果当前某个Item会Remove,原本屏幕外的Item就可能Move到屏幕上,这个Item的信息也需要被记录,pre-layout就是为这类Item提供了显示动画的能力。

来看下mViewInfoStore.addToPreLayout(holder, animationInfo)做了什么。可以发现它通过键值对<ViewHolder, InfoRecord>保存Item的信息,并且由于当前保存的是动画前的Item信息,为InfoRecord添加FLAG_PRE标识。

    void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info;
        record.flags |= FLAG_PRE;
    }

InfoRecord的数据结构如下,在dispatchLayoutStep1()中保存的是preInfo

    static class InfoRecord {
        static final int FLAG_DISAPPEARED = 1;
        static final int FLAG_APPEAR = 1 << 1;
        static final int FLAG_PRE = 1 << 2;
        static final int FLAG_POST = 1 << 3;
        static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
        static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
        static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
        int flags;
        @Nullable
        RecyclerView.ItemAnimator.ItemHolderInfo preInfo;
        @Nullable
        RecyclerView.ItemAnimator.ItemHolderInfo postInfo;
    }
dispatchLayoutStep2()

再来看dispatchLayoutStep2(),它主要通过mLayout.onLayoutChildren(mRecycler, mState)对新的数据进行了布局,随后对是否支持动画进行检查并赋值。

	private void dispatchLayoutStep2() {
        // ......
        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
    }
dispatchLayoutStep3()

dispatchLayoutStep3()对Change动画进行了特殊处理,如果是Change动画会直接执行。对于其余动画来说,会先记录动画后的Item信息,记录完毕后触发动画。

    private void dispatchLayoutStep3() {
        // 初始化...
        if (mState.mRunSimpleAnimations) {
            // Step 3: Find out where things are now, and process change animations.
            // traverse list in reverse because we may call animateChange in the loop which may
            // remove the target view holder.
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                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()) {
                    // 执行CHANGE动画
                    final boolean oldDisappearing = mViewInfoStore.isDisappearing(
                            oldChangeViewHolder);
                    final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                    if (oldDisappearing && oldChangeViewHolder == holder) {
                        // 如果1个Item CHANGED,但是更新后会消失,则执行disappear动画
                        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 {
                    // 将Layout后的信息保存到mViewInfoStore中
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                }
            }
            // 触发动画
            mViewInfoStore.process(mViewInfoProcessCallback);
        }
        // clean up...
    }

这里通过mViewInfoStore.addToPostLayout(...)将Layout后的信息保存,再执行mViewInfoStore.process(mViewInfoProcessCallback)触发动画,主要逻辑为根据之前保存的item信息执行对应的回调。

    void process(ProcessCallback callback) {
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                // Appeared then disappeared. Not useful for animations.
                callback.unused(viewHolder);
            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                // 被LayoutManager设置为消失
                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) {
                // preLayout和postLayout都在,执行callback.processPersistent
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE) != 0) {
                // 在pre-layout中,但是不在post-layout中,因此item消失了
                callback.processDisappeared(viewHolder, record.preInfo, null);
            } else if ((record.flags & FLAG_POST) != 0) {
                // 不在pre-layout,出现在了post-layout
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_APPEAR) != 0) {
                // Scrap view. RecyclerView will handle removing/recycling this.
            }
            InfoRecord.recycle(record);
        }
    }

回调mViewInfoProcessCallback如下所示,基本就是执行对应的方法。

    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
                @Override
                public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, @Nullable ItemHolderInfo postInfo) {
                    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) {
                        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);
                }
            };

我们以animateDisappearance(...)为例来分析,如果mItemAnimator对应的方法返回true的话就执行postAnimationRunner(),该方法就是将mItemAnimatorRunner放到下一帧执行,而mItemAnimatorRunner实际调用了mItemAnimator.runPendingAnimations()执行了一段时间内触发的所有动画。它们的代码如下所示。

    void animateDisappearance(@NonNull ViewHolder holder,
                              @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        addAnimatingView(holder);
        holder.setIsRecyclable(false);
        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
            postAnimationRunner();
        }
    }

    void postAnimationRunner() {
        if (!mPostedAnimatorRunner && mIsAttached) {
            // 将mItemAnimatorRunner放到下一帧执行
            ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
            mPostedAnimatorRunner = true;
        }
    }

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

动画触发的大体逻辑就是这样,不过为了加深印象,我们再举个栗子详细描述下。还是以REMOVE动画为例,来逐步分析下方动画执行时保存了哪些信息,又是怎么判断动画类型的。

gif-例子.gif

上面提到,dispatchLayoutStep1()方法会将动画执行前的ItemHolderInfo保存至ViewInfoStore,那么上方的例子在动画前会保存5个ItemHolderInfo,由于是VERTICAL布局,只关注ItemHolderInfo的top和bottom即可。
下方的ViewHolder(0)等表示保存时的Key,括号中的数字为该ViewHolder对应ItemView显示的数据

ViewHolder(0) : InfoRecord->preInfo(top: 0,   bottom: 131)
ViewHolder(1) : InfoRecord->preInfo(top: 131, bottom: 262)
ViewHolder(2) : InfoRecord->preInfo(top: 262, bottom: 393)
ViewHolder(3) : InfoRecord->preInfo(top: 393, bottom: 524)
ViewHolder(4) : InfoRecord->preInfo(top: 524, bottom: 655)

随后dispatchLayoutStep2()调用mLayout.onLayoutChildren(mRecycler, mState)进行布局。布局完毕后执行dispatchLayoutStep3(),开始保存Layout之后的ItemHolderInfo,此时有4个Item,它们的信息会被保存至InfoRecord的postInfo中,最终ViewInfoStore中mLayoutHolderMap的信息如下所示。

ViewHolder0 : InfoRecord->preInfo(top: 0,   bottom: 131), postInfo(top: 0, bottom: 131)
ViewHolder1 : InfoRecord->preInfo(top: 131, bottom: 262), postInfo(null)
ViewHolder2 : InfoRecord->preInfo(top: 262, bottom: 393), postInfo(top: 131, bottom: 262)
ViewHolder3 : InfoRecord->preInfo(top: 393, bottom: 524), postInfo(top: 262, bottom: 393)
ViewHolder4 : InfoRecord->preInfo(top: 524, bottom: 655), postInfo(top: 393, bottom: 524)

随后执行mViewInfoStore.process(mViewInfoProcessCallback)开始动画,这里通过判断preInfo和postInfo是否存在去执行对应的回调,下面的代码只保留了本次例子相关的部分。

    void process(ProcessCallback callback) {
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                // ......
            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                // ......
            } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
                // ......
            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                // preInfo和postInfo都有,执行callback.processPersistent(...)
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE) != 0) {
                // 有preInfo没有postInfo,说明Item被移除了,执行callback.processDisappeared()
                callback.processDisappeared(viewHolder, record.preInfo, null);
            } else if ((record.flags & FLAG_POST) != 0) {
                // ......
            } else if ((record.flags & FLAG_APPEAR) != 0) {
                // ......
            }
            InfoRecord.recycle(record);
        }
    }

先来看callback.processPersistent(),由于整个dataSet并未改变,因此进入else代码块,根据mItemAnimator.animatePersistence(...)的返回值决定是否执行postAnimationRunner()

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

mItemAnimator.animatePersistence(...)执行的是SimpleItemAnimator中重写的方法,如下所示。根据上面总结的ViewInfoStore中保存的值,对于ViewHolder(2)、ViewHolder(3)和ViewHolder(4)来说,preInfo.top和postInfo.top不相等,执行animateMove(...),最终会执行DefaultItemAnimator中重写的方法,将MOVE动画添加到待执行动画列表中。而对于ViewHolder(0)来说,preInfo和postInfo中的值相等,就不用执行动画。

    @Override
    public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder,
                                      @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
            return animateMove(viewHolder,
                    preInfo.left, preInfo.top, postInfo.left, postInfo.top);
        }
        dispatchMoveFinished(viewHolder);
        return false;
    }

再来看callback.processDisappeared(),直接执行了animateDisappearance(...)

    @Override
    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                            @Nullable ItemHolderInfo postInfo) {
        mRecycler.unscrapView(viewHolder);
        animateDisappearance(viewHolder, info, postInfo);
    }

也是根据mItemAnimator.animateDisappearance()的返回值决定是否执行postAnimationRunner()

    void animateDisappearance(@NonNull ViewHolder holder,
                              @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        addAnimatingView(holder);
        holder.setIsRecyclable(false);
        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
            postAnimationRunner();
        }
    }

mItemAnimator.animateDisappearance()执行了SimpleItemAnimator中重写的方法。

    @Override
    public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
                                        @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        int oldLeft = preLayoutInfo.left;
        int oldTop = preLayoutInfo.top;
        View disappearingItemView = viewHolder.itemView;
        int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
        int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
        if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
            disappearingItemView.layout(newLeft, newTop,
                    newLeft + disappearingItemView.getWidth(),
                    newTop + disappearingItemView.getHeight());
            }
            return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
        } else {
            return animateRemove(viewHolder);
        }
    }

由于viewHolder.isRemoved()返回true,因此执行animateRemove(viewHolder),最终执行DefaultItemAnimator中重写的方法,将动画添加到了执行队列中。
到此,我们的例子也就讲完了。

二、自定义动画

DefaultItemAnimator提供的动画效果还是比较完善的,如果还有其他需求的话,在animateRemoveImpl(...)animateAddImpl(...)这样的方法中修改动画效果即可。方法内部通过ViewPropertyAnimator实现具体的动画效果,修改起来比较简单,例如我们可以将DefaultItemAnimator的Remove动画修改为:Item缩小至消失。

    private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
        // ......
        animation.setDuration(getRemoveDuration()).scaleX(0).scaleY(0).setListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                        dispatchRemoveStarting(holder);
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        animation.setListener(null);
                        view.setScaleX(1);
                        view.setScaleY(1);
                        dispatchRemoveFinished(holder);
                        mRemoveAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }

真的超简单啊有没有!!!来看下效果。

gif-自定义.gif

emmmm…效果不怎么好看,不过只要能设计出优美的动画,自定义ItemAnimator是一件很简单的事情。

三、参考

  1. RecyclerView.ItemAnimator终极解读(一)–RecyclerView源码解析
  2. RecyclerView机制解析: ChildHelper
  3. RecyclerView剖析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值