android 资产管理动图,这可能是你见过的迄今为止最简单的RecyclerView Item加载动画...

如何实现RecyclerView Item动画?

这个问题想必有很多人都会讲,我可以用ItemAnimator实现啊,这是RecyclerView官方定义的接口,专门扩展Item动画的,那我为什么要寻求另外一种方法实现呢?因为最近反思了一个问题,其实很多人都有这个思维定律,那就是官方的一定是好的,真的是这样吗?下面我来从另一个角度说明官方的ItemAnimator是真的不好用

ItemAnimator 弃用理由

理由一

f6f8a321bccb?hmsr=toutiao.io

image

f6f8a321bccb?hmsr=toutiao.io

image

第一张图是最牛逼的星星最多的wasabeef/recyclerview-animators,基类有713行代码,你知道这个类打包出来多大吗?有20多kb,相当恐怖的好吗?

第二个是官方提供的默认动画,也是将近700行

理由是:代码过于臃肿

理由二

既然我想用ItemAnimator接口,且官方有一个DefaultItemAnimator,为什么我不能扩展DefaultItemAnimator,而是要实现SimpleItemAnimator,写个700行代码才能够捋明白一个Item的动画?总之,当我知道继承SimpleItemAnimator后,实现的和DefaultItemAnimator几乎一样的时候,我内心是拒绝的,我不想看到这些冗余的代码,也许是我有那么一点点洁癖

理由是:复用率太低,感觉官方根本没当回事(也许是我学习没到位,没有看到它好的地方)

理由三

notifyDataSetChanged不支持ItemAnimator动画,我不讨论这么设计是真个好和坏,但起码它是我不选择ItemAnimator的另一个理由

等等吧,不知道还能吐槽什么了?虽然有这些缺点,可我们总会遇到不得不用的时候,你说呢?

layoutAnimation 弃用理由

这个用的比较少吧,大部分都是在用ItemAnimator,我们直接看个例子,然后再说为什么要弃用它

step1

android:duration="@integer/anim_duration_medium">

android:fromYDelta="-20%"

android:toYDelta="0"

android:interpolator="@android:anim/decelerate_interpolator"

/>

android:fromAlpha="0"

android:toAlpha="1"

android:interpolator="@android:anim/decelerate_interpolator"

/>

android:fromXScale="105%"

android:fromYScale="105%"

android:toXScale="100%"

android:toYScale="100%"

android:pivotX="50%"

android:pivotY="50%"

android:interpolator="@android:anim/decelerate_interpolator"

/>

step2

xmlns:android="http://schemas.android.com/apk/res/android"

android:animation="@anim/item_animation_fall_down"

android:delay="15%"

android:animationOrder="normal"

/>

step3

int resId = R.anim.layout_animation_fall_down;

LayoutAnimationController animation = AnimationUtils.loadLayoutAnimation(ctx, resId);

recyclerview.setLayoutAnimation(animation);

很简单对吧,当我运行demo的时候,似乎看起来效果很好哦,可最后我才发现一些问题,我决定不使用它了

理由一

动画只加载第一屏?这个能不能改观我没有深入研究哦,可这样一个效果我也无法忍受,但我上啦的时候,为什么下面未显示的Item动画就没了呢?这就是我弃用的理由

理由二

当我用GridLayoutManger 的时候,我还要在定义一套 layhoutAnimation,虽然只是增加了一个xml文件,可这比起我接下来介绍的实现方案,那就差了一个档次,所以我选择弃用

最简单的Animation动画方案

这个方案的优势:

代码超级简洁

动画的定制度更高,没一个Item都可以轻松的且变着花样的加载动画

可实现预加载动画,可实现更新的动画

轻松实现一个接一个的加载动画

缓存更加轻量级,减少内存开销

缺点:

当然也有缺点,这个看具体使用场景的取舍,也许是可以支持的,但目前我还没有想到如何做到。

没有删除动画

没有移动动画

对的目前就这俩。

实现原理

很简单,就是给View加载一个Animation,通过xml配置

代码实现

step1

从下往上移动的动画

android:duration="@integer/anim_duration_long">

android:interpolator="@android:anim/accelerate_decelerate_interpolator"

android:fromYDelta="50%p"

android:toYDelta="0"

/>

android:fromAlpha="0"

android:toAlpha="1"

android:interpolator="@android:anim/accelerate_decelerate_interpolator"

/>

step2

//从零开始计数,用来实现一个接一个的延迟动画(简单点就是:在一个加载一半时,下一个才执行)

private var delayPosition = 0

//缓存Animation,避免重复loadAnimation,减少开销

private val animationArray = SparseArray()

//加载xml动画,并放入缓存中

private fun loadAnimation(context: Context, @AnimRes itemAnimationRes: Int, key: Int): Animation {

return animationArray[key] ?: AnimationUtils.loadAnimation(context, itemAnimationRes).apply {

animationArray.append(key, this)

}

}

//清理缓存

fun RecyclerView.onDestroy(){

animationArray.clear()

}

//执行动画

fun RecyclerView.ViewHolder.animationWithDelayOffset(

isEnableAnimation: Boolean,

@AnimRes itemAnimationRes: Int,

delayOffset: Int

) {

if (isEnableAnimation) {

//清理调之前的动画

itemView.clearAnimation()

//当前item positon

val currentPosition = ++delayPosition

//计算下一个Item需要delay的时间

val delay = currentPosition * delayOffset / 2

itemView.animation =

loadAnimation(itemView.context, itemAnimationRes, currentPosition).apply {

//延迟多久开始执行

startOffset = delay.toLong()

//加载完成后将计数制成零

setAnimationListener(object : Animation.AnimationListener {

override fun onAnimationRepeat(p0: Animation?) {

}

override fun onAnimationEnd(p0: Animation?) {

delayPosition = 0

}

override fun onAnimationStart(p0: Animation?) {

}

})

//如果已经执行一次才会调用start,因为第一次用AnimationUtils.loadAnimation加载的时候会自动执行一次

if (this.hasEnded()) {

this.start()

}

}

}

}

//这个是我的DefaultViewHolder,我可以拿到ViewModel是否是第一次isFirstInit,这样就可以实现只有第一次初始化后才会执行哦

fun DefaultViewHolder.firstAnimation(

@AnimRes itemAnimationRes: Int = R.anim.item_animation_from_right,

delayOffset: Int = 200

) = animationWithDelayOffset(

getViewModel()?.isFirstInit ?: false,

itemAnimationRes,

delayOffset

)

//拿到ViewModel是否是第一次isFirstInit,这样就可以实现只有第二次加载执行哦

fun DefaultViewHolder.updateAnimation(

@AnimRes itemAnimationRes: Int = R.anim.item_animation_scale

) = animation(!(getViewModel()?.isFirstInit ?: false), itemAnimationRes)

代码里有个细节处理《加载完成后将计数制成零》,这里是因为,你第一次进页面的时候,所有的Item的都按照顺序执行完毕后,由于delay数很大,导致你滑动的时候,会出现很久才加载进来的动画哦,这里我是想用handler的postdelay实现,这样就可以做到只要有接着的动画执行,就不会被重制成0,保证下次动画的执行一定是在上一个Item的后面,这样确实是一个问题,也许在我验证够多的场景后就切过去了,嘿嘿。但目前来看,这个效果实现的很满意,慢慢重构和完善。当然我的这种实现方式,确实是比较简单而且效果还不错,既有LayoutAnimation的影子,又有ItemAnimator的功能,岂不是很不错。

step3

应用,一行代码搞定

f6f8a321bccb?hmsr=toutiao.io

image

配置更新时候的动画,一行搞定

f6f8a321bccb?hmsr=toutiao.io

image

感觉到简单了吧,这么顺滑的动画实现,你不想体验下吗?

Demo地址,欢迎体验

接下来会解决什么问题?

这么用难道确实比ItemAnimator好吗?当然会有一些问题吧,比如什么时候需要中断,什么时候需要重新加载,甚至到底什么时候清理掉缓存更合理呢?之后还需要一些更全面的实战来解决这问题,也会借助ItemAnimator的实现原则来考虑当前动画如何做到合理的生命周期管理。

作者

i校长

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值