安卓動畫(三)Recyclerview ItemAnimator(上)

安卓動畫(三)Recyclerview ItemAnimator(下)

前面講了補間動畫、Transition動畫,屬性動畫,這一篇講一講和Recyclerview相關的動畫。
Recyclerview裏面有一個類ItemAnimator專門用來管理每個item變化時所對應的動畫,當item被刪除、添加、位置發生變化時,相應的動畫就會被調用,從而給用戶更好的體驗,這一篇就原理、流程做一個大概的介紹。

由於ItemAnimator的流程還是比較複雜的,因爲和recyclerview的layout過程是有一定耦合的而且涉及到ViewHolder的複用,因此只能簡單的講一下ItemAnimator的封裝過程。ItemAnimator這個類裏面有這麼幾個重要的方法:

public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
                @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
                @NonNull ViewHolder newHolder,
                @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
                @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
                @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);

上面幾個方法看名字和源碼註釋就可以確定分別和view的出現、消失、改變等狀態對應,比如當一個item要被remove掉,那麼就要對這個ViewHolder調用animateDisappearance方法。然後還有運行動畫的方法

public abstract void runPendingAnimations();

這是一個抽象方法,需要實現,主要是觸發動畫的開始,由於ItemAnimator這個類比較抽象,安卓爲我們封裝了一個類SimpleItemAnimator繼承自RecyclerView.ItemAnimator,而且還拋出了幾個抽象方法,需要我們實現:

public abstract boolean animateAdd(RecyclerView.ViewHolder holder);
public abstract boolean animateRemove(RecyclerView.ViewHolder holder);
public abstract boolean animateChange(RecyclerView.ViewHolder oldHolder,
            RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
public abstract boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY,
            int toX, int toY);

然而這幾個方法使用起來還是不好使用,所以系統又基於SimpleItemAnimator擴展了DefaultItemAnimator,幸好有這個類,否則實現自定義動畫幾乎不可能,我們看系統的這個DefaultItemAnimator大致流程是什麼,以添加一個item爲例:

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

animateAdd方法被調用時,animation其實還沒開始,可以看做是add動畫進行之前的一個初始化過程,可以看出animateAdd方法將view的alpha值設置成了0,這也是爲什麼默認添加一個item時,item是有一個逐漸變爲不透明的效果的。具體add的動畫是在animateAddImpl中開啓的

animation.alpha(1).setDuration(getAddDuration()

截取關鍵代碼,上面有alpha(1),這句話就是要把view從當前狀態變成一個alpha爲1的動畫,這也證實了默認動畫的效果了。
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) {
            // nothing to animate
            return;
        }
        // First, remove stuff
        //最先開啓的是remove動畫
        for (RecyclerView.ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder);
        }
        mPendingRemovals.clear();
        // Next, move stuff
        if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear();
            Runnable mover = new Runnable() {
                @Override
                public void run() {
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                    moves.clear();
                    mMovesList.remove(moves);
                }
            };
            //如果有remove動畫,那麼等remove動畫結束,再開始move動畫
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }
        // Next, change stuff, to run in parallel with move animations
        if (changesPending) {
            final ArrayList<ChangeInfo> changes = new ArrayList<>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear();
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    changes.clear();
                    mChangesList.remove(changes);
                }
            };
            //如果有remove動畫,那麼等remove動畫結束,再開始change動畫
            if (removalsPending) {
                RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
        // Next, add stuff
        if (additionsPending) {
            final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
            additions.addAll(mPendingAdditions);
            mAdditionsList.add(additions);
            mPendingAdditions.clear();
            Runnable adder = new Runnable() {
                @Override
                public void run() {
                    for (RecyclerView.ViewHolder holder : additions) {
                        animateAddImpl(holder);
                    }
                    additions.clear();
                    mAdditionsList.remove(additions);
                }
            };
            //最後等所有動畫結束之後,再開始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動畫其次(可同時進行)、等前面所有動畫結束之後,再開始add動畫,爲什麼默認的animator要這樣寫,我估計是爲了刷新界面的時候不至於太亂,其實我們可以選擇性的使動畫同時進行,沒必要使move動畫非要在remove動畫完成之後再開始,在後面我們自定義animator時會講。
DefaultItemAnimator的主要細節就講完了,現在結合recyclerview的layout過程來看看動畫和layout的順序,recyclerview有三個方法:

dispatchLayoutStep1
dispatchLayoutStep2
dispatchLayoutStep3

step1爲preLayout階段,在這一步裏面收集各個item的信息,調用ItemAnimator.recordPreLayoutInformation對每一個item記錄信息,默認只記錄四條邊的位置信息,放在ItemHolderInfo這個類裏面,然後Step2進行真正的layout過程,layout完成之後,Step3中對每一個item調用mItemAnimator.recordPostLayoutInformation再次記錄信息,這樣動畫才能知道每個item的最終狀態。這時已經有了layout前後兩次viewholde的信息,此時就要對比每一個viewholder決定對viewholder是做add、remove還是其他動畫,調用mViewInfoStore.process(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) {
                        // 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);
                }
            };

最終就調用到上面DefaultItemAnimator中的方法了,然後開啓動畫,關於Recyclerview ItemAnimator的整體流程就分析到這裏,下一篇結合相應的例子改進DefaultItemAnimator這個類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章