美文网首页
自定义RecyclerView动画——实现remove飞出效果

自定义RecyclerView动画——实现remove飞出效果

作者: chzphoenix | 来源:发表于2018-01-03 11:30 被阅读552次

    我们经常会遇到在一个list中删除一条数据,这时候一般会有一个飞出的动画效果,如下图:


    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却瞬间上移导致重叠。效果如下:


    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();
        }
    }
    

    相关文章

      网友评论

          本文标题:自定义RecyclerView动画——实现remove飞出效果

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