RecyclerView的基本使用与进阶(三)

回头看看自己的博客发现RecyclerVer系列还没写完。。完全忘记了。。。来补上,本篇分为两个部分:

第一部分: DefaultItemAnimator源码解析,即Google官方提供的item动画源码

第二部分: 如何写一个自己的item动画
本篇就不贴效果图了,想看效果不妨自己去试试,我一直认为要学会怎么熟练的使用一个东西,就得了解这个东西是怎么运作的,不多说废话,正式开始:

第一部分

打开源码可以看到该类继承自SimpleItemAnimator,再点开这个类看看,发现继承自RecyclerView.ItemAnimator,看来是SimpleItemAnimator对rv的动画进行了进一步的封装,最后DefaultItemAnimator再来具体实现想要的动画,这里不详谈SimpleItemAnimator,只看Google是怎么实现rv的item动画的,说一下涉及到的几个方法的作用:

方法解读

  1. animateAdd(final RecyclerView.ViewHolder holder):添加item时调用的动画,用adapter.notifyItemInster()时会调用该方法,若需要定制自己的动画,需要将返回值更改为true
  2. animateRemove(final RecyclerView.ViewHolder holder):删除item时调用的动画,用adapter.notifyItemRemove()时会调用该方法,若需要定制自己的动画,需要将返回值更改为true
  3. animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY):item移动时调用的动画,再说明白点就是添加、删除item时调用的方法,参数看名字就知道啥意思,不多做解释,若需要定制自己的动画,需要将该返回值更改为true
  4. animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY):item接收到notifyItemChanged()或者notifyItemRangeChanged()调用的方法,同样,若需要定制自己的动画,需要将该返回值更改为true
  5. endAnimation(RecyclerView.ViewHolder item):动画结束时调用的方法
  6. canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder, List payloads):当item改变时调用该方法itemAnimator可以决定是否要重复使用相同的ViewHolder进行动画。
  7. isRunning():判断是否有动画正在执行
  8. runPendingAnimations():当有等待动画时调用

    变量解读

    private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
    private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
    private ArrayList mPendingMoves = new ArrayList<>();
    private ArrayList mPendingChanges = new ArrayList<>();

    ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>();
    ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>();
    ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>();
    ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>();

    ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>();
    ArrayList<ArrayList> mMovesList = new ArrayList<>();
    ArrayList<ArrayList> mChangesList = new ArrayList<>();
    能看到Google用了这么多的集合,具体作用是什么?很简单,看看runPendingAnimations()方法,我们可以得出一个结论:动画时有可能存在等待情况的!也就是说,当上一个动画还未执行完成的时候,下一个动画需要等待上一个动画执行完成后才可以执行,那万一等待了多个动画咋整,用集合保存起来呀,可是增加、删除、移动、刷新就四个动画,为啥有这么多集合,仔细看看前8个,是不是两两一组的,作用是什么呢?拿Add这组解释一下:可以看到Add这组有两个集合,mPendingAdditions和mAddAnimations,前者是等待处理的动画集合,后者就是处理中的动画集合,其他几个一样。那剩下三个是干嘛的?后面用到的时候再解释

isRunning()

@Override
public boolean isRunning() {
    return (!mPendingAdditions.isEmpty() ||
            !mPendingChanges.isEmpty() ||
            !mPendingMoves.isEmpty() ||
            !mPendingRemovals.isEmpty() ||
            !mMoveAnimations.isEmpty() ||
            !mRemoveAnimations.isEmpty() ||
            !mAddAnimations.isEmpty() ||
            !mChangeAnimations.isEmpty() ||
            !mMovesList.isEmpty() ||
            !mAdditionsList.isEmpty() ||
            !mChangesList.isEmpty());
}

这个似乎没啥好解释的。。只要其中一个动画集合不为空,则返回true,即:还有动画正在执行或需要执行

animateAdd()

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

一行一行的来解读,首先是resetAnimation(holder),这个方法是用来干嘛的,点进去看看:

private void resetAnimation(RecyclerView.ViewHolder holder) {
    AnimatorCompatHelper.clearInterpolator(holder.itemView);
    endAnimation(holder);
}

第一个方法是用来干嘛的?层层抽丝剥茧,终于找到这样一段:

 @Override
    public void clearInterpolator(View view) {
        if (mDefaultInterpolator == null) {
            mDefaultInterpolator = new ValueAnimator().getInterpolator();
        }
        view.animate().setInterpolator(mDefaultInterpolator);
    }
}

看到了吗!view.animate().setInterpolator(),这是拿来干嘛的?

官方解释:

Sets the interpolator for the underlying animator that animates the requested properties.

翻译一下:为底层动画设置内插器,使动态化请求的属性。

具体作用是啥?就是用来修饰动画效果的,点到即止,不多做展开,来看看下一句

endAnimation(),调用结束动画

@Override
    public void endAnimation(RecyclerView.ViewHolder item) {
        final View view = item.itemView;
        ViewCompat.animate(view).cancel();
        ...
        for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
            ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
            if (additions.remove(item)) {
                ViewCompat.setAlpha(view, 1);
                dispatchAddFinished(item);
                if (additions.isEmpty()) {
                    mAdditionsList.remove(i);
                }
            }
        }
        ...
        }

这一段就是将动画停止掉,将整个animateAdd方法连起来看看:

  1. 开始执行添加动画时,重置添加相关的动画
  2. 将需要执行添加动画的itemView的alpha值设置为0,即设置为不可见
  3. 将需要执行添加动画的itemView添加进等待处理添加动画的集合中
    这时候就会执行runPendingAnimations()方法:

    runPendingAnimations()

    public void runPendingAnimations() {

        boolean removalsPending = !mPendingRemovals.isEmpty();//如果待执行的删除动画为空时
        boolean movesPending = !mPendingMoves.isEmpty();//如果待执行的移动动画为空时
        boolean changesPending = !mPendingChanges.isEmpty();//如果待执行的刷新动画为空时
        boolean additionsPending = !mPendingAdditions.isEmpty();//如果待执行的添加动画为空时
        if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
            // 没有动画需要执行时
            return;
        }
        // 首先,执行删除动画
        for (RecyclerView.ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder);
        }
        mPendingRemovals.clear();
        // 然后执行移动动画
        if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear();
            Runnable mover = new Runnable() {
                @Override
                public void run() {
                //将所有的删除动画放在一块一起执行
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                    //执行完成后清空
                    moves.clear();
                    mMovesList.remove(moves);
                }
            };
            //如果有待执行的删除动画
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }
        // 执行更新动画,与移动动画并行执行
        if (changesPending) {
            final ArrayList<ChangeInfo> changes = new ArrayList<>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear();
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                //将所有待执行的更新动画一块执行
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    //执行完成后清空
                    changes.clear();
                    mChangesList.remove(changes);
                }
            };
            //如果有待执行的删除动画
            if (removalsPending) {
                RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
        // 执行添加动画
        if (additionsPending) {
            final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
            additions.addAll(mPendingAdditions);
            mAdditionsList.add(additions);
            mPendingAdditions.clear();
            Runnable adder = new Runnable() {
                @Override
                public void run() {
                //将所有添加动画放在一块执行
                    for (RecyclerView.ViewHolder holder : additions) {
                        animateAddImpl(holder);
                    }
                    //动画执行完成后清空
                    additions.clear();
                    mAdditionsList.remove(additions);
                }
            };
            //如果删除、移动、更新动画某一个为空时,获取每一个动画的持续时间
            if (removalsPending || movesPending || changesPending) {
                long removeDuration = removalsPending ? getRemoveDuration() : 0;
                long moveDuration = movesPending ? getMoveDuration() : 0;
                long changeDuration = changesPending ? getChangeDuration() : 0;
                //添加动画的持续时间 = =删除动画持续时间+(移动动画或更新动画持续时间最长的一个)
                long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
                View view = additions.get(0).itemView;
                ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
            } else {
                adder.run();
            }
        }
    }
    

以上就是处理待执行动画的所有步骤,有详细注释就不做解释了,细心的同学可能有注意到,添加、删除、移动、更新动画都有一个impl方法,这个方法里就是具体的动画!先来看看添加动画:

animateAddImpl

void animateAddImpl(final RecyclerView.ViewHolder holder) {
    final View view = holder.itemView;
    final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
    mAddAnimations.add(holder);
    animation.alpha(1).setDuration(getAddDuration()).
            setListener(new VpaListenerAdapter() {
                //动画开始时,调用dispatchAddStarting,即通知可以开始动画了!
                @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();
}

简单讲讲animateMove:

animateMove()

@Override
    public boolean animateMove(final RecyclerView.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;
    }

其实很好理解,就说一下deltaX和deltaY,Google这样计算的目的就是为了在上一个item在移出的时候,当前这个item不会立即移动到当前上一个item的位置。

其他几个动画类似,就不做解释了,在这里也可以看出,具体是用的什么动画,动画时什么效果,都是在animatexxximpl中执行的,如果想要定制自己的动画,直接改这几个方法就可以了

接下来讲讲如何定制属于自己的item动画

第二部分

前面说了,动画的具体实现都是在各个animateXXXImpl()中,那么要定制属于我们自己item动画,只需要更改animateXXXImpl()方法就可以了,那么我们先来做一个自己的添加动画

animateRemoveImpl()

期望效果:这个item删除时,逐渐透明且慢慢缩小
很简单的效果,我们只需要设置alpha,scaleX和scaleY就可以了:

private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
    final View view = holder.itemView;
    final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
    mRemoveAnimations.add(holder);
    animation.setDuration(getRemoveDuration())
            .alpha(0).scaleY(0).scaleX(0).setListener(new VpaListenerAdapter() {
        @Override
        public void onAnimationStart(View view) {
            dispatchRemoveStarting(holder);
        }

        @Override
        public void onAnimationEnd(View view) {
            animation.setListener(null);
            ViewCompat.setAlpha(view, 1);
            ViewCompat.setScaleY(view, 1);
            ViewCompat.setScaleX(view, 1);
            dispatchRemoveFinished(holder);
            mRemoveAnimations.remove(holder);
            dispatchFinishedWhenDone();
        }
    }).start();
}

animateAddImpl()

期望效果:从透明变为可见,并从小拉伸到正常大小
其实就是跟remove反过来了而已,来看看代码:

void animateAddImpl(final RecyclerView.ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        mAddAnimations.add(holder);
        animation.alpha(1).scaleX(1).scaleY(1).setDuration(getAddDuration()).
                setListener(new VpaListenerAdapter() {
                    @Override
                    public void onAnimationStart(View view) {
                        dispatchAddStarting(holder);
                    }

                @Override
                public void onAnimationCancel(View view) {
                    ViewCompat.setScaleX(view, 1);
                    ViewCompat.setScaleY(view, 1);
                    ViewCompat.setAlpha(view, 1);
                }

                @Override
                public void onAnimationEnd(View view) {
                    animation.setListener(null);
                    dispatchAddFinished(holder);
                    mAddAnimations.remove(holder);
                    dispatchFinishedWhenDone();
                }
            }).start();
}

来看看效果:

添加Item的动画跟想象的不一样啊!回头看看animateAdd():

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

这里还开始动画时只设置了alpha为透明,并没有把item缩小,那么改一下:

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

再来看看效果:

这下对了,快速连续点几下试试看呢:
为啥前几个item都是透明的啊!刷新看看,又正常显示了!咋回事,想想最开始说的animateAdd(),我们在添加动画之前,先重置了动画,也就是resetAnimation(),点进去找到endAnimation(),看看添加相关的代码块:

for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
            ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
            if (additions.remove(item)) {
                ViewCompat.setAlpha(view, 1);
                dispatchAddFinished(item);
                if (additions.isEmpty()) {
                    mAdditionsList.remove(i);
                }
            }

这下原因明了了,在我们快速点击添加时,会先执行这里面的代码,而这里只设置了alpha值,修改一下:

for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
            ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
            if (additions.remove(item)) {
                ViewCompat.setAlpha(view, 1);
                ViewCompat.setScaleY(view,1);
                ViewCompat.setScaleX(view,1);
                dispatchAddFinished(item);
                if (additions.isEmpty()) {
                    mAdditionsList.remove(i);
                }
            }

再来看看效果:

完美

结语

只讲了添加删除的动画,move和change都没有改动,篇幅太长就不做演示了,有兴趣可以自己尝试一下,不过难度最大的还是move,change的动画,毕竟要计算坐标,相对添加删除来说要难一点点,虽然也差不了多少,本篇到此结束,文中如有错误的地方,还请指出,谢谢!

坚持原创技术分享,您的支持将鼓励我继续创作!