最简单的RecyclerView Item动画全解析

“Recycler Item是有动画的!” 笔者被扇了一巴掌,“你这特x不是废话吗,我是想知道Item动画怎么做!”

hahahaha…

皮一下很开心。怎么做呢,其实根据不同的效果,可以选择难度不同的实现方式,本篇文章就是讲这个的!为了有更直观的感受,笔者用RecyclerView做了个聊天场景的demo:

需要源码的同学,请转到 github上下载

目录

  • RecyclerView是有动画的
  • 最简单的Item动画
  • 深入定制

一、RecyclerView是有动画的

尼采说,你要理解一样东西,必须先证明其存在。RecyclerView就算什么也不做其实也是有一个默认的ItemAnimator的,只是平常不会发现而已。要明显的感觉出来,不妨把动画的时间拉长。

//配置为默认动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//设置添加、移除时间为1s
mRecyclerView.getItemAnimator().setAddDuration(1000);
mRecyclerView.getItemAnimator().setRemoveDuration(1000);

我们把添加和移除动画各设置为1s,感觉到了吗,分别是逐渐消失和显现的动画。

本篇博文重点不在于属性动画的使用方式,但不可避免地需要使用属性动画。文章中涉及的动画类为ViewPropertyAnimator,链式调用极其简单,相信谁都可以看明白的。

比如,我要把一个view1秒内x轴360度式丢出100米远:

 view.animate().setDuration(1000)//时间
 			   .rotation(360)//旋转360度
               .translationX(100)//X轴位移
               .start();

简单吧,那下面就是笔者的干货了。


二、最简单的Item动画

如果你已经在代码中有一定时间的浸淫一段时间了,那你一定听过一句古话:不会用轮子的老司机不是好的程序员。对的!这里会推荐一个强大又简单的轮子,github - RecyclerView Animators

本节的内容是基于此的,如果你断言它不足以满足需求,可以直接跳到三、深入定制

2.0 依赖

如果因为android.x报错,可以选择依赖旧版本,如下

implementation 'jp.wasabeef:recyclerview-animators:2.3.0'

2.1 如何使用

轮子提供了一系列的Item动画,分别是Scale(缩放)、Fade (渐变)、Flip(翻转)、Slide(滑动)。详见github链接,下翻到【Animators】

例如Slide就有:
SlideInLeftAnimator, SlideInRightAnimator, OvershootInLeftAnimator, OvershootInRightAnimator
SlideInUpAnimator,SlideInDownAnimator

可以望文生义了,SlideInUpAnimator就是向上滑动出现的意思,我们来试一试:

 //设置上浮动画
 mRecyclerView.setItemAnimator(new SlideInUpAnimator());
 
 //时长设置500ms,便于观察
 mRecyclerView.getItemAnimator().setAddDuration(500);
 mRecyclerView.getItemAnimator().setRemoveDuration(500);

注意,所有的Item动画,必须使用局部刷新来显示,即

notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)

简单的尝试,已经可以看到效果了

2.2 定制动画

上面的效果是有缺陷的,Robot在撤回时,下滑消失的效果明显是不合适的。怎么办?工程师是要解决一切问题的!现在,我们需要定制动画了。

来做一个360度上丢出现的动画吧~

同样需要用到轮子,我们继承于BaseItemAnimator,这个是上面提到的SlideInUpAnimator等动画的父类,选择实现其中 Remove/移除、Move/移动、Add/添加、Change/改变 的动画方法就可以定制了。

Remove动画定制:

@Override
protected void preAnimateRemoveImpl(RecyclerView.ViewHolder holder) {
    super.preAnimateRemoveImpl(holder);
    //实现移除动画前的处理
    
}

@Override
protected void animateRemoveImpl(RecyclerView.ViewHolder holder) {
    //执行移除动画
    holder.itemView.animate()
            .alpha(0f)//逐渐透明
            .setDuration(getRemoveDuration())//使用默认的移除时间
            .start();
}

我们并不需要在移除前做任何处理,所以在preAnimateRemoveImpl(…)方法中没做任何事情,在animateRemoveImpl(Holder holder)方法实现移除动画:item逐渐透明。

Add动画定制:

@Override
protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
    super.preAnimateAddImpl(holder);
	//添加动画前调用

    //提前设置item的Y轴偏移,透明度为0。
    holder.itemView.setTranslationY(translationY);
    holder.itemView.setAlpha(0f);
}

@Override
protected void animateAddImpl(RecyclerView.ViewHolder holder) {
	//执行添加动画
    holder.itemView.animate()
            .setDuration(getAddDuration())
            .translationY(0)//回到原位置
            .rotation(360)//旋转360度
            .alpha(1f)//透明度显现
            .start();
}

这段代码有一点长,但同样简单。我们在添加Item前,提前设置了Y轴偏移量,和透明度为0,这样在执行动画时,通过位置和透明度恢复,就可以得到从下往上显现的效果了。 当然,说好的360度旋转进场是必不可少的 ( ˙˘˙ )

辛苦了这么久,看下效果吧


三、深入定制

前面我们已经解决了Item动画使用和简单定制的问题,相信你已经可以自行设计出漂亮的动画了。但是!可能会有超出你设想的情况。上栗子:

对话开始了!笔者在每条消息后, 滑动到最后一项(空白Foot)以保证显示。看出问题了吗?动画并不是连贯的,Item是先进行了位移,再进行了添加动画。 这对我们 吹毛求疵(误) 注重体验的程序员是不允许的,笔者想以此为引子,谈谈深入定制Item动画的思路。

难度加大警告

我们需要追本溯源,去实现更原始的动画类了。第一节的DefaultItemAnimator、第二节的BaseItemAnimator都是继承于SimpleItemAnimator。我们来看看SimpleItemAnimator需要重写的方法:
在这里插入图片描述
有些多,重写这么多方法,完全这绝不是笔者的本意。建议大家继承SimpleItemAnimator的时候若非必要,可直接复制
BaseItemAnimator或者DefaultItemAnimator代码

有一些区别:

  • BaseItemAnimator 必须重新实现 animateRemoveImpl()/移除animateAddImpl()/添加 这两个抽象方法
  • DefaultItemAnimator 需要修改少量final错误,无动画执行前/Prexxx方法。

请酌情使用那种方式按图索骥。

复制粘贴的小窍门

  • 先拷贝到如Notepad++等编辑器,再拷贝到AS中,可以避免自动切换对象所属类的麻烦
  • AS默认快捷键 Ctrl + R,可以进行文本内替换

3.0 实现动画的方式是一样的

复制BaseItemAnimator或者DefaultItemAnimator代码来继承SimpleItemAnimator,并没有改变定制动画的方式。仍可以找到以下方法:

//移除动画
private void animateRemoveImpl(final ViewHolder holder) {..}
//添加动画
void animateAddImpl(final ViewHolder holder){..}
//位移动画
void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY){..}
//改变动画
void animateChangeImpl(final CustomAnimator.ChangeInfo changeInfo){..}

按需修改里面的内容,去实现动画效果。

3.1 打破Move和Add的时间壁垒

之前的动画实现方式,默认Move/滑动动画完成后才会进行Add/添加动画。为解决这个问题,笔者将带大家看复制代码中真正安排执行动画的方法:runPendingAnimations()

runPendingAnimations()中有四段,依次处理执行remove、move、change、add。四段大同小异,我们来看Add/添加部分:

...
 // Next, add stuff
 if (additionsPending) {
	 ....
	 
	 //addAnimator执行的runnable
     Runnable adder = new Runnable() {
         public void run() {
             boolean removed = mAdditionsList.remove(additions);
             //已取消
             if (!removed) {
                 // already canceled
                 return;
             }	
			 //执行所有存储的添加动画
             for (ViewHolder holder : additions) {
                 doAnimateAdd(holder);
             }
             additions.clear();
         }
     };
     
     //remove、move、change,有任何一种动画正在执行,则延迟到其完成
     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;
         //延迟并执行添加动画的runnable
         ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
     } else {
     	 //没有其余动画,直接执行runnable
         adder.run();
     }
 }

代码很长,但工程师是要迎难而上的!仔细看看,分两个部分:

  • 创建的Runnable adder中放入Add动画开始的逻辑
  • 判断是否有其他动画,并在其他动画完成后再执行Runnable adder

好了,那笔者知道怎么改,使move和add动画同时开始了。

 long moveDuration = 0;

由于是上浮缓慢显现,笔者还 在此方法中Move动画,和滑至最后一项做了延迟,使其平滑与Add动画衔接。 这里没有贴出来,最终效果见文末。

~~,多说一句,runPendingAnimations()是一切Item动画的开始,如果你对需要实现的效果还没什么思路,可以从runPendingAnimations()往下跟逻辑。

3.2 动画试图超出RecyclerView范围

一鼓作气,我们把仅剩的瑕疵也看了。

如果是因为其他图片遮挡,导致滑动或动画时如上图。怎么解决呢,抱歉的是 Item动画是不能超过RecyclerView范围去实现的

我们可以扩大Recycler的范围,并使用透明渐变蒙层的方式去解决。(请参考另一篇文章:逐渐消失的Item! —— RecyclerView隐藏与渐变的实现

谢谢大家,我们看下最终的效果吧
最终效果

四、写在最后

好了,Robot受到了惩罚,我们也学到了不少东西。笔者可能会有疏漏的地方,但如果有一些帮助的话,点个赞鼓励下吧 ~。
Robt

发布了12 篇原创文章 · 获赞 36 · 访问量 4776

猜你喜欢

转载自blog.csdn.net/weixin_42229694/article/details/103513003