美文网首页源码解析Material Design 学习面试汇总
RecyclerView源码分析(四)--动画流程

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

作者: 柴泽建_Jack | 来源:发表于2016-09-07 23:18 被阅读2149次

    看完本文你大概需要 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!!

    相关文章

      网友评论

      • Poan:LZ还在不?有个问题请教一下
        recyclerview 条目点击时局部刷新(有个展开动画),有个偶发的问题是点了局部刷新后没有任何效果,打断点看了就是因为mAdapterHelpter.onItemRange 里的返回值mPendingUpdates.size() 大于1 了,每次点击这个size就+1 这样就导致一直不刷新 .
        能帮忙分析下原因吗 ? 刷新都是在主线程进行的.

      本文标题:RecyclerView源码分析(四)--动画流程

      本文链接:https://www.haomeiwen.com/subject/rthsettx.html