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

weixin_34194087發表於2018-01-03

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


2030586-42f9f2790d3c61f5
image

在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卻瞬間上移導致重疊。效果如下:


2030586-e11fe4f2a806ec9f
image

這時因為我們目前只定義了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();
    }
}

相關文章