自定義RecyclerView動畫——實現remove飛出效果

我們經常會遇到在一個list中刪除一條數據,這時候一般會有一個飛出的動畫效果,如下圖:

在RecyclerView中可以通過setItemAnimator函數設置一個ItemAnimator,實現item的add、remove、change等動作的動效。下面我們就通過ItemAnimator來實現上面的效果。

首先創建一個類,繼承至SimpleItemAnimator,如下:
class FlyAnimator extends SimpleItemAnimator{
    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        return false;
    }
    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        return false;
    }
    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        return false;
    }
    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
        return false;
    }
    @Override
    public void runPendingAnimations() {
    }
    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
    }
    @Override
    public void endAnimations() {
    }
    @Override
    public boolean isRunning() {
        return false;
    }
}
SimpleItemAnimator是一個抽象類,需要實現幾個函數。
因爲我們要實現是一個remove的動作,需要在animateRemove中處理。這裏我們參照DefaultItemAnimator的做法,首先需要兩個list,然後在animateRemove將holder添加進list中,這裏暫時不做處理,如下:
List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>();
List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>();
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
    removeHolders.add(holder);
    return true;
}
至於另外一個list下面會用到。
既然我們在animateRemove函數中不做動效處理,那麼應該在哪裏處理?
答案是在runPedingAnimations中來處理,代碼如下:
@Override
public void runPendingAnimations() {
    if(!removeHolders.isEmpty()) {
        for(RecyclerView.ViewHolder holder : removeHolders) {
            remove(holder);
        }
        removeHolders.clear();
    }
}
遍歷removeHolders,依次執行remove,這個函數是自定義的,用於執行動畫,代碼如下:
private void remove(final RecyclerView.ViewHolder holder){
    removeAnimators.add(holder);
    TranslateAnimation animation = new TranslateAnimation(0, 1000, 0, 0);
    animation.setDuration(500);
    animation.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
            dispatchRemoveStarting(holder);
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            removeAnimators.remove(holder);
            dispatchRemoveFinished(holder);
            if(!isRunning()){
                dispatchAnimationsFinished();
            }
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });
    holder.itemView.startAnimation(animation);
}
可以看到就是對holder的itemview執行來一個移動動畫。
這裏使用removeAnimators來管理所有的remove動畫,目前是判斷所有的remove動畫是否結束,這個判斷在isRunning函數中,代碼如下:
@Override
public boolean isRunning() {
    return !(removeHolders.isEmpty() && removeAnimators.isEmpty());
}
當兩個list都爲空的時候,所有動畫都完成了,回到remove代碼中這時候執行disPatchAnimationsFinished函數。

通過上面幾步,實現了remove的動效,當我們執行的時候發現確實有了飛出的效果,但是下面的item卻瞬間上移導致重疊。效果如下:

這時因爲我們目前只定義了remove的效果,實際上不僅有飛出的動作還有一個上移的動作,所以還需要定義一下move的效果,同remove一樣需要兩個list,在animateMove函數中將holder添加至list中,如下:
List<RecyclerView.ViewHolder> moveHolders = new ArrayList<>();
List<RecyclerView.ViewHolder> moveAnimators = new ArrayList<>();
@Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
    holder.itemView.setTranslationY(fromY - toY);
    moveHolders.add(holder);
    return true;
}
注意在remove的一瞬間,下方的item實際上就已經上移了,所以在animateMove中設置item的translationY使其保持在未上移的位置。
然後同樣在runPedingAnimations中處理,這時runPedingAnimations代碼如下:
@Override
public void runPendingAnimations() {
    if(!removeHolders.isEmpty()) {
        for(RecyclerView.ViewHolder holder : removeHolders) {
            remove(holder);
        }
        removeHolders.clear();
    }
    if(!moveHolders.isEmpty()){
        for(RecyclerView.ViewHolder holder : moveHolders) {
            move(holder);
        }
        moveHolders.clear();
    }
}
這裏move同樣是自定義的一個函數,代碼如下:
private void move(final MoveInfo moveInfo){
    moveAnimators.add(moveInfo);
    ObjectAnimator animator = ObjectAnimator.ofFloat(moveInfo.holder.itemView,
            "translationY", moveInfo.holder.itemView.getTranslationY(), 0);
    animator.setDuration(500);
    animator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(android.animation.Animator animation) {
            dispatchMoveStarting(moveInfo.holder);
        }

        @Override
        public void onAnimationEnd(android.animation.Animator animation) {
            dispatchMoveFinished(moveInfo.holder);
            moveAnimators.remove(moveInfo.holder);
            if(!isRunning()) dispatchAnimationsFinished();
        }
    });
    animator.start();
}
執行了一個屬性動畫,修改了item的translationY使其上移回原位置。同時注意修改isRunning函數,如下:
@Override
public boolean isRunning() {
    return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty());
}
這樣就實現了一開始的飛出效果。

總結一下,其實自定義ItemAnimator比較簡單,雖然代碼接近百行,但其實主要就是執行動畫。需要注意的就是有些情況需要配合move的動作。

自定義ItemAnimator後,直接爲RecyclerView設置即可:
list.setItemAnimator(new FlyAnimator());
設置後如果調用了adapter的notifyItemRemoved函數就會執行remove的動效。

完整代碼如下:
class FlyAnimator extends SimpleItemAnimator{
    List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>();
    List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>();
    List<RecyclerView.ViewHolder> moveHolders = new ArrayList<>();
    List<RecyclerView.ViewHolder> moveAnimators = new ArrayList<>();
    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        removeHolders.add(holder);
        return true;
    }
    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        return false;
    }
    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        holder.itemView.setTranslationY(fromY - toY);
        moveHolders.add(holder);
        return true;
    }
    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
        return false;
    }
    @Override
    public void runPendingAnimations() {
        if(!removeHolders.isEmpty()) {
            for(RecyclerView.ViewHolder holder : removeHolders) {
                remove(holder);
            }
            removeHolders.clear();
        }
        if(!moveHolders.isEmpty()){
            for(RecyclerView.ViewHolder holder : moveHolders) {
                move(holder);
            }
            moveHolders.clear();
        }
    }
    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
    }
    @Override
    public void endAnimations() {
    }
    @Override
    public boolean isRunning() {
        return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty());
    }

    private void remove(final RecyclerView.ViewHolder holder){
        removeAnimators.add(holder);
        TranslateAnimation animation = new TranslateAnimation(0, 1000, 0, 0);
        animation.setDuration(500);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                removeAnimators.remove(holder);
                dispatchRemoveFinished(holder);
                if(!isRunning()){
                    dispatchAnimationsFinished();
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        holder.itemView.startAnimation(animation);
    }

    private void move(final RecyclerView.ViewHolder holder){
        moveAnimators.add(holder);
        ObjectAnimator animator = ObjectAnimator.ofFloat(holder.itemView,
                "translationY", holder.itemView.getTranslationY(), 0);
        animator.setDuration(500);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(android.animation.Animator animation) {
                dispatchMoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(android.animation.Animator animation) {
                dispatchMoveFinished(holder);
                moveAnimators.remove(holder);
                if(!isRunning()) dispatchAnimationsFinished();
            }
        });
        animator.start();
    }

}


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