RecyclerView 之 ItemAnimator,item 添加移除动画效果

RecyclerView 中的列表中,当某个数据被移除刷新时,我们想让它对应的item移除时有个自定义动画效果怎么办?RecyclerView 对外提供了 setItemAnimator(ItemAnimator animator) 方法

    ItemAnimator mItemAnimator = new DefaultItemAnimator();
    
    public void setItemAnimator(ItemAnimator animator) {
        if (mItemAnimator != null) {
            mItemAnimator.endAnimations();
            mItemAnimator.setListener(null);
        }
        mItemAnimator = animator;
        if (mItemAnimator != null) {
            mItemAnimator.setListener(mItemAnimatorListener);
        }
    }

ItemAnimator 类中有几个重要方法:

animateAppearance(): 当 item 出现在屏幕上时被调用(可能是add或move)。
animateDisappearance(): 当 item 消失在屏幕上时被调用(可能是remove或move)。

animateChange(): 在 调用 notifyItemChanged() 或 notifyDataSetChanged() 时被调用。
animatePersistence(): 在不是调用 notifyItemChanged() 和 notifyDataSetChanged() 的情况下布局发生改变时被调用。

runPendingAnimations(): 动画执行
isRunning(): 是否有动画要执行或正在执行。
dispatchAnimationsFinished(): 当全部动画执行完毕时被调用。


从RecyclerView 看到,有个默认的 DefaultItemAnimator 动画,如果有自定义的,则会替换默认的,看看 DefaultItemAnimator 是什么,它继承了 SimpleItemAnimator 这个抽象类,SimpleItemAnimator 中有些抽象方法,好些方法上都有注释,告诉我们这个方法的意思及要调用某些方法,DefaultItemAnimator 是个例子,里面有比较详细的细节及逻辑,我们可以写个子类,继承 SimpleItemAnimator,把一些关键方法先写个默认值


public abstract class ItemAnimatorAbstrc extends SimpleItemAnimator {
    // item 移除回调
    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        return false;
    }

    // item 添加回调
    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        return false;
    }


    // 添加,移动更新时,其它 item 的动画执行
    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        return false;
    }

    // item 更新回调
    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
        return false;
    }

    // 控制执行动画的地方
    @Override
    public void runPendingAnimations() {

    }

    // 停止某个 item 的动画
    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {

    }

    // 停止所有 item 动画
    @Override
    public void endAnimations() {

    }

    @Override
    public boolean isRunning() {
        return false;
    }
}

我们写的这个类里面,没有抽象方法,但我还是给他加了个 abstract 来修饰,意思是它不能直接被使用,必须使用它的子类,这个类是个基类,仅仅是提供说明而已。一般来说,添加 item和移除item会比较多一点,所以重点看看 animateRemove()、 animateAdd()、 animateMove()、 runPendingAnimations() 这四个方法即可,比如说移除一个item,此时我们需要在
animateRemove() 方法中接收对应的 ViewHolder,然后在 runPendingAnimations() 方法中执行我们自定义的动画,并且在动画开始和结束时,分别调用 dispatchRemoveStarting() 和 dispatchAnimationsFinished() 方法,为什么不在 animateRemove() 方法中执行动画操作呢?因为我们可以存在多个动画同时执行的过程,如果按照上面的逻辑,item 在删除执行动画时,其它的 item 都已经自动往上位移了,体验很差,我们可以在 animateMove() 方法中让item位移还原,在 runPendingAnimations() 中与删除动画一起执行其他item的向上位移的操作。写个简单的item被删除操作


public class RemoveAnimator extends ItemAnimatorAbstrc {

    List removeHolders = new ArrayList<>();
    List removeAnimators = new ArrayList<>();

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


    @Override
    public void runPendingAnimations() {
        if (!removeHolders.isEmpty()) {
            for (RecyclerView.ViewHolder holder : removeHolders) {
                remove(holder);
            }
            removeHolders.clear();
        }
    }


    @Override
    public boolean isRunning() {
        return !(removeHolders.isEmpty() && removeAnimators.isEmpty());
    }

    private void remove(final RecyclerView.ViewHolder holder) {
        removeAnimators.add(holder);
        ObjectAnimator animator = ObjectAnimator.ofFloat(holder.itemView, "translationX", 0, holder.itemView.getMeasuredWidth());
        animator.setDuration(500);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(android.animation.Animator animation) {
                dispatchRemoveStarting(holder);
            }

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

}

这样,一个粗糙的item删除动画就完成了。如果我们想让添加和删除效果好看一点,可以仿照 DefaultItemAnimator 来写一下, animateRemove()、 animateAdd()、 animateMove() 中向集合中添加元素时,一定要把返回值值为true,animateMove() 中是位移,我们为了不那么突兀,需要把item移动到它被删除前的位置。 runPendingAnimations() 中执行的顺序,先是删除,然后是move和change并行 ,最后是添加,对于移动和删除,我们把item当做一个普通的view,用属性动画即可,这样就实现了透明度及位置的变化,产生动画效果。有一点要注意,比如 move 时,在动画开始时,需要调用 dispatchMoveStarting()方法,在动画结束时需要调用 dispatchMoveFinished() 方法,并检查如果所有的动画都停止了,还需要调用 dispatchAnimationsFinished() 方法;add 和 remove 及 change 也是同样。

public class TestAnimator extends ItemAnimatorAbstrc {

    List removeHolders = new ArrayList<>();
    List removeAnimators = new ArrayList<>();
    List moveHolders = new ArrayList<>();
    List moveAnimators = new ArrayList<>();

    ArrayList mPendingAdditions = new ArrayList<>();
    ArrayList mAddAnimations = new ArrayList<>();

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

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

    @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();
        }
        if (!mPendingAdditions.isEmpty()) {
            for (RecyclerView.ViewHolder holder : mPendingAdditions) {
                add(holder);
            }
            mPendingAdditions.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()
                && mPendingAdditions.isEmpty() && mAddAnimations.isEmpty()
        );
    }

    private void remove(final RecyclerView.ViewHolder holder) {
        removeAnimators.add(holder);
        ObjectAnimator animator = ObjectAnimator.ofFloat(holder.itemView, "translationX", 0,         holder.itemView.getMeasuredWidth());
        animator.setDuration(500);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(android.animation.Animator animation) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(android.animation.Animator animation) {
                removeAnimators.remove(holder);
                dispatchRemoveFinished(holder);
                dispatchFinishedWhenDone();
            }
        });
        animator.start();
    }


    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) {
                moveAnimators.remove(holder);
                dispatchMoveFinished(holder);
                dispatchFinishedWhenDone();
            }
        });
        animator.start();
    }

    private void add(final RecyclerView.ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        mAddAnimations.add(holder);
        animation.alpha(1).setDuration(500).
                setListener(new ViewPropertyAnimatorListener() {
                    @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);
                        mAddAnimations.remove(holder);
                        dispatchAddFinished(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }


    private void dispatchFinishedWhenDone() {
        if (!isRunning()) {
            dispatchAnimationsFinished();
        }
    }


}

这是个简化版的item添加移除动画,如果这个里面对应的方法都了解了,再去阅读 DefaultItemAnimator 代码就容易多了,里面做的更严谨,细节处理的更完美。

你可能感兴趣的:(Android,知识)