Android RecyclerView 详解(三) RecyclerView的动画实现(移除、添加、改变、移动)






一丶添加删除时候的刷新问题


先上一下效果图吧


RecyclerView条目点击 android android recyclerview item动画_Android


1.为了方便起见我们还是先添加三个按钮分别实现添加删除和改变




2.在Adapter中写调用方法并进行刷新

public void remove(int position){
        list.remove(position);
        notifyItemRemoved(position);
    }
    public void add(int position,String data) {
        list.add(position,data);
        notifyItemInserted(position);
    }
    public void change(int position,String data) {
        list.remove(position);
        list.add(position,data);
        notifyItemChanged(position);
    }


可以发现他和ListView的刷新不同的是,对应不同的删除添加或者改变对应的notify都是不同的,如果不这样的话那么添加删除改变的动画将没有。




3.在Mainactivity中调用方法就好


case R.id.btn_add:
                myAdapter.add(0,"新加数据");
                myRecyclerView.scrollToPosition(0);
                break;
            case R.id.btn_delete:
                myAdapter.remove(0);
                break;
            case R.id.btn_change:
                myAdapter.change(0,"改变的数据");
                break;


注意!!!可以发现增加的时候多了一个方法,那么这个方法是定位显示的条目位置的,当你新添加方法的时候他不会显示出来,需要将条目位置进行设定。





二、对原动画进行更改达到自己想要的动画




1.如何设置原动画

//设置默认动画
        DefaultItemAnimator animator = new DefaultItemAnimator();
        //设置动画时间
        animator.setAddDuration(2000);
        animator.setRemoveDuration(2000);
        myRecyclerView.setItemAnimator(animator);



2.设置自己自定义的动画




(1)首先这些动画的方法都是私有的所以我们没有办法去重写他,那么我们可以新建一个属于自己的动画类来实现自定义动画


(2)自定义动画的实现(依然为了方便起见我们添加四个按钮来动态的设置自定义动画方便对比)


a.首先我们需要建一个自己的动画类将原本的那个代码全部复制过来


b.删除动画(滑动消失)


先来效果图吧


RecyclerView条目点击 android android recyclerview item动画_移动_02


源码

@Override
    public boolean animateRemove(final ViewHolder holder) {
        resetAnimation(holder);
        mPendingRemovals.add(holder);
        return true;
    }


这段代码是将移除动画添加到集合当中




真正执行移除的动画代码如下(源码)

private void animateRemoveImpl(final ViewHolder holder) {
        final View view = holder.itemView;//首先得到ItemView
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);//开启一个属性动画
        mRemoveAnimations.add(holder);//加入到要删除的VIew中
/*        animation.setDuration(getRemoveDuration())//设置延迟时间
                .alpha(0).setListener(new VpaListenerAdapter() {//listener是开始和结束的监听*/
        animation.setDuration(getRemoveDuration())//设置延迟时间
                .translationX(view.getWidth()).setListener(new VpaListenerAdapter() {//listener是开始和结束的监听
            //变到哪里
            @Override
            public void onAnimationStart(View view) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(View view) {
                animation.setListener(null);//当动画结束的时候要将监听置为空
                //ViewCompat.setAlpha(view, 1);//因为有复用布局的问题,所以你将控件删除的时候需要将他还原,要不会出现重复问题
                ViewCompat.setTranslationX(view, 0);//因为有复用布局的问题,所以你将控件删除的时候需要将他还原,要不会出现重复问题
                dispatchRemoveFinished(holder);
                mRemoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }




我们可以发现他的动画是先淡出然后上移,我们想要实现让他啊滑动出屏幕后其他条目上移的话那么简单的将淡入淡出动画改成移动动画即可

private void animateRemoveImpl(final ViewHolder holder) {
        final View view = holder.itemView;//首先得到ItemView
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);//开启一个属性动画
        mRemoveAnimations.add(holder);//加入到要删除的VIew中
        animation.setDuration(getRemoveDuration())//设置延迟时间
                .translationX(view.getWidth()).setListener(new VpaListenerAdapter() {//listener是开始和结束的监听
            @Override
            public void onAnimationStart(View view) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(View view) {
                animation.setListener(null);//当动画结束的时候要将监听置为空
                //ViewCompat.setAlpha(view, 1);//因为有复用布局的问题,所以你将控件删除的时候需要将他还原,要不会出现重复问题
                ViewCompat.setTranslationX(view,0);//因为有复用布局的问题,所以你将控件删除的时候需要将他还原,要不会出现重复问题
                dispatchRemoveFinished(holder);
                mRemoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }


在点击事件中设置成自己的动画和动画延时时间就好

myItemAnimator = new MyItemAnimator();
                myItemAnimator.setRemoveDuration(2000);
                myRecyclerVIew.setItemAnimator(myItemAnimator);






c.添加时的动画


先来效果图


RecyclerView条目点击 android android recyclerview item动画_动画_03


原动画是先下移然后再从最淡的时候显示出来那么我们想让他从侧面进入那么更改淡入淡出动画换成移动动画即可


源码

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

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




更改后的代码

@Override
    public boolean animateAdd(final ViewHolder holder) {
        resetAnimation(holder);
        //ViewCompat.setAlpha(holder.itemView, 0);
        ViewCompat.setTranslationX(holder.itemView,-holder.itemView.getWidth());
        mPendingAdditions.add(holder);
        return true;
    }

    void animateAddImpl(final ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        mAddAnimations.add(holder);
        //animation.alpha(1).setDuration(getAddDuration()).
        animation.translationX(0).setDuration(getAddDuration()).
                setListener(new VpaListenerAdapter() {
                    @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);
                        dispatchAddFinished(holder);
                        mAddAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }




在点击事件中设置动画和其时间即可

myItemAnimator = new MyItemAnimator();
                myItemAnimator.setAddDuration(2000);
                myRecyclerVIew.setItemAnimator(myItemAnimator);






d.移动动画,所谓的移动动画就是当你删除或者添加的时候其他条目的动画,我们可以将其设置成翻转的效果


效果图如下


RecyclerView条目点击 android android recyclerview item动画_Android_04


源码

@Override
    public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
                               int toX, int toY) {
        final View view = holder.itemView;
        fromX += ViewCompat.getTranslationX(holder.itemView);
        fromY += ViewCompat.getTranslationY(holder.itemView);
        resetAnimation(holder);
        int deltaX = toX - fromX;
        int deltaY = toY - fromY;
        if (deltaX == 0 && deltaY == 0) {
            dispatchMoveFinished(holder);
            return false;
        }
        if (deltaX != 0) {
            ViewCompat.setTranslationX(view, -deltaX);
        }
        if (deltaY != 0) {
            ViewCompat.setTranslationY(view, -deltaY);
        }
        mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
        return true;
    }

    void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        final View view = holder.itemView;
        final int deltaX = toX - fromX;
        final int deltaY = toY - fromY;
        if (deltaX != 0) {
            ViewCompat.animate(view).translationX(0);
        }
        if (deltaY != 0) {
            ViewCompat.animate(view).translationY(0);
        }
        // TODO: make EndActions end listeners instead, since end actions aren't called when
        // vpas are canceled (and can't end them. why?)
        // need listener functionality in VPACompat for this. Ick.
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        mMoveAnimations.add(holder);
        animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
            @Override
            public void onAnimationStart(View view) {
                dispatchMoveStarting(holder);
            }
            @Override
            public void onAnimationCancel(View view) {
                if (deltaX != 0) {
                    ViewCompat.setTranslationX(view, 0);
                }
                if (deltaY != 0) {
                    ViewCompat.setTranslationY(view, 0);
                }
            }
            @Override
            public void onAnimationEnd(View view) {
                animation.setListener(null);
                dispatchMoveFinished(holder);
                mMoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }




在animateMoveImpl中添加我们的翻转动画


animation.rotationXBy(180);


因为我们是翻转180度那么需要在后面进行重置


在AnimationEnd后重置

ViewCompat.setRotationX(view,0);



更改后的所有代码


void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        final View view = holder.itemView;
        final int deltaX = toX - fromX;
        final int deltaY = toY - fromY;
        if (deltaX != 0) {
            ViewCompat.animate(view).translationX(0);
        }
        if (deltaY != 0) {
            ViewCompat.animate(view).translationY(0);
        }
        // TODO: make EndActions end listeners instead, since end actions aren't called when
        // vpas are canceled (and can't end them. why?)
        // need listener functionality in VPACompat for this. Ick.
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        animation.rotationXBy(180);
        mMoveAnimations.add(holder);
        animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
            @Override
            public void onAnimationStart(View view) {
                dispatchMoveStarting(holder);
            }
            @Override
            public void onAnimationCancel(View view) {
                if (deltaX != 0) {
                    ViewCompat.setTranslationX(view, 0);
                }
                if (deltaY != 0) {
                    ViewCompat.setTranslationY(view, 0);
                }
            }
            @Override
            public void onAnimationEnd(View view) {
                animation.setListener(null);
                ViewCompat.setRotationX(view,0);
                dispatchMoveFinished(holder);
                mMoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }




e.改变动画,就是移除就得得到新的,而源码是淡出旧的显示新的那么进行适当更改就行了


效果图如下:


RecyclerView条目点击 android android recyclerview item动画_移动_05


源码

@Override
    public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
                                 int fromX, int fromY, int toX, int toY) {
        if (oldHolder == newHolder) {
            // Don't know how to run change animations when the same view holder is re-used.
            // run a move animation to handle position changes.
            return animateMove(oldHolder, fromX, fromY, toX, toY);
        }
        final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
        final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
        final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
        resetAnimation(oldHolder);
        int deltaX = (int) (toX - fromX - prevTranslationX);
        int deltaY = (int) (toY - fromY - prevTranslationY);
        // recover prev translation state after ending animation
        ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
        ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
        ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
        if (newHolder != null) {
            // carry over translation values
            resetAnimation(newHolder);
            ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
            ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
            ViewCompat.setAlpha(newHolder.itemView, 0);
        }
        mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
        return true;
    }

    void animateChangeImpl(final ChangeInfo changeInfo) {
        final ViewHolder holder = changeInfo.oldHolder;
        final View view = holder == null ? null : holder.itemView;
        final ViewHolder newHolder = changeInfo.newHolder;
        final View newView = newHolder != null ? newHolder.itemView : null;
        if (view != null) {
            final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
                    getChangeDuration());
            mChangeAnimations.add(changeInfo.oldHolder);
            oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
            oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
            oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
                @Override
                public void onAnimationStart(View view) {
                    dispatchChangeStarting(changeInfo.oldHolder, true);
                }

                @Override
                public void onAnimationEnd(View view) {
                    oldViewAnim.setListener(null);
                    ViewCompat.setAlpha(view, 1);
                    ViewCompat.setTranslationX(view, 0);
                    ViewCompat.setTranslationY(view, 0);
                    dispatchChangeFinished(changeInfo.oldHolder, true);
                    mChangeAnimations.remove(changeInfo.oldHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }




更改后的代码

@Override
    public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
                                 int fromX, int fromY, int toX, int toY) {
        if (oldHolder == newHolder) {
            // Don't know how to run change animations when the same view holder is re-used.
            // run a move animation to handle position changes.
            return animateMove(oldHolder, fromX, fromY, toX, toY);
        }
        final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
        final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
        final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
        resetAnimation(oldHolder);
        int deltaX = (int) (toX - fromX - prevTranslationX);
        int deltaY = (int) (toY - fromY - prevTranslationY);
        // recover prev translation state after ending animation
        ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
        ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
        ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
        if (newHolder != null) {
            // carry over translation values
            resetAnimation(newHolder);
            ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
            ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
            //ViewCompat.setAlpha(newHolder.itemView, 0);
            //新的移除视线
            ViewCompat.setTranslationX(newHolder.itemView,-newHolder.itemView.getWidth());
        }
        mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
        return true;
    }

    void animateChangeImpl(final ChangeInfo changeInfo) {
        final ViewHolder holder = changeInfo.oldHolder;
        final View view = holder == null ? null : holder.itemView;
        final ViewHolder newHolder = changeInfo.newHolder;
        final View newView = newHolder != null ? newHolder.itemView : null;
        if (view != null) {
            final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
                    getChangeDuration());
            mChangeAnimations.add(changeInfo.oldHolder);
            oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
            oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
            //oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
            //老的移除
            oldViewAnim.translationX(view.getWidth()).setListener(new VpaListenerAdapter() {
                @Override
                public void onAnimationStart(View view) {
                    dispatchChangeStarting(changeInfo.oldHolder, true);
                }

                @Override
                public void onAnimationEnd(View view) {
                    oldViewAnim.setListener(null);
                    ViewCompat.setAlpha(view, 1);
                    ViewCompat.setTranslationX(view, 0);
                    ViewCompat.setTranslationY(view, 0);
                    dispatchChangeFinished(changeInfo.oldHolder, true);
                    mChangeAnimations.remove(changeInfo.oldHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
        if (newView != null) {
            final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
            mChangeAnimations.add(changeInfo.newHolder);
            newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
                    alpha(1).setListener(new VpaListenerAdapter() {
                @Override
                public void onAnimationStart(View view) {
                    dispatchChangeStarting(changeInfo.newHolder, false);
                }
                @Override
                public void onAnimationEnd(View view) {
                    newViewAnimation.setListener(null);
                    ViewCompat.setAlpha(newView, 1);
                    ViewCompat.setTranslationX(newView, 0);
                    ViewCompat.setTranslationY(newView, 0);
                    dispatchChangeFinished(changeInfo.newHolder, false);
                    mChangeAnimations.remove(changeInfo.newHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
    }





源码:源码链接