上面一篇文章写了 View动画 和 帧动画。。。。。
那这篇文章,就来一波属性动画。。。。。
我们应该都知道,View动画改变的只是View绘制的位置,并没有改变View的属性,比如四个顶点的坐标。就是你把一个View从他原始位置A,移动到新的位置B并停留在在B,你点击B位置,不是不会有点击事件的响应的。比如要点位置A才有。。。。
所以如果一个动画是有位置移动,但又必须有点击事件的处理的话,View动画就满足不了需求了。。。
但是 属性动画 可以弥补View动画这个缺陷!!!!!!
属性动画是Android3.0 引入的,可以说是 对 View 动画的 一种扩张,是View动画的加强版,,,或者说几乎可以完全替代 View动画,而且属性动画的可以使任何对象,不单止是View。。。。
因为 3.0以后才能用,如果想在3.0前面的版本用,可以使用开源库:NineOldAndroids
属性动画,其实就是两个类,ValueAnimator 和 ObjectAnimator
ValueAnimator :
设置开始值和结束值,还有一个总时间。 获取这个时间内,每一个时间节点的过度值,通过值去控制动画目标,其实这种场景用的不多。。。。。。。
ObjectAnimator :
这个用的比较多,也基本上是用这个去控制动画。可以直接以任务对象为动画对象。。。。
============【ValueAnimator 】============
先说 ValueAnimator
使用:ValueAnimator.ofFloat()
/**
* ValueAnimator
* */
private fun valueAnimator() {
// 从 0 过渡到 1
val valueAnimator = ValueAnimator.ofFloat(0F, 1F)
// 时间是 100毫秒
valueAnimator.duration = 100
// 监听进度
valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(animation: ValueAnimator?) {
//获取动画进度值,通过这个值,去控制动画目标
//比如 一个View背景颜色的百分比
val animatedValue = animation?.animatedValue as Float
Log.d(TAG, "进度:$animatedValue")
}
})
valueAnimator.start()
}
看下日志:
从结果看到,多次回调 这个过渡进度的。。。。。
我们再看下 ValueAnimator.ofFloat()的源码
可以传进去多个参数的,所以:
如果 ValueAnimator.ofFloat(0F, 1F) 代表从 0过渡到1,
如果 ValueAnimator.ofFloat(0F, 50F,3F,100F) 代表从 0过渡到50,再从50过度到3,然后再从3过渡到100
上代码:
/**
* ValueAnimator
* */
private fun valueAnimator() {
// 从 0 过渡到 1
val valueAnimator = ValueAnimator.ofFloat(0F, 50F,3F,100F)
// 时间是 100毫秒
valueAnimator.duration = 1000
// 监听进度
valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(animation: ValueAnimator?) {
//获取动画进度值,通过这个值,去控制动画目标
//比如 一个View背景颜色的百分比
val animatedValue = animation?.animatedValue as Float
Log.d(TAG, "进度:$animatedValue")
}
})
valueAnimator.start()
}
看结果:
进度:0.0
进度:0.0
进度:0.09472668
进度:0.40268898
进度:0.9233728
进度:1.655303
进度:2.535273
进度:3.670764
进度:5.009651
进度:6.4521832
进度:8.17451
进度:10.087408
进度:12.056963
进度:14.323733
进度:16.763512
进度:19.211622
进度:21.966991
进度:24.87359
进度:27.73996
进度:30.916098
进度:34.217968
进度:37.43199
进度:40.950714
进度:44.566513
进度:48.049103
进度:48.285706 // 接近50时,开始从 50 往 3F去过度
进度:44.67541
进度:41.22999
进度:37.528618
进度:33.79583
进度:30.263401
进度:26.500002
进度:22.736603
进度:19.204176
进度:15.471386
进度:11.770042
进度:8.324593
进度:4.7142982 // 接近3时,开始从 3 往 100F去过度
进度:6.3578963
进度:13.540961
进度:20.555607
进度:27.381924
进度:33.617188
进度:40.022766
进度:46.184475
进度:51.745235
进度:57.384045
进度:62.423447
进度:67.47879
进度:72.21197
进度:76.36032
进度:80.43042
进度:84.14144
进度:87.29662
进度:90.28128
进度:92.878716
进度:94.96303
进度:96.78872
进度:98.20865
进度:99.17078
进度:99.79256
进度:100.0
除了 ValueAnimator.ofFloat外,用的比较多的还有:ValueAnimator.ofInt()
原理是一样的。就不一一展示了。。。。。。
那么除此之外,我们还可以调用
setStartDelay()方法来设置动画延迟播放的时间,
setRepeatCount() 设置动画循环播放的次数
setRepeatMode()循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。
这些方法都很简单,就不再进行详细讲解了。
============【ObjectAnimator 】==========
ObjectAnimator 就不一样了,可以对任何对象进行动画设置,比如:View的背景颜色,还有alpha值。
从源码看,ObjectAnimator 是 继承 ValueAnimator,
所以说到底,都是以 ValueAnimator 设置值的 方式去操作动画。所以ValueAnimator 是整个属性动画的核心。
所以 ValueAnimator 的方法,在 ObjectAnimator 中几乎都能用。。。。所以 ObjectAnimator 几乎可以完全替代ValueAnimator 的。。。。
其实,ObjectAnimator 用法,也差不多,看代码----
我们先看下 透明度(alpha)
private fun objectAnimator() {
/**
* 参数1 tv_animator_view 目标View
* 参数2 需要进行动画的属性 比如:alpha ,这个参数字符乱传会怎样? 后面会说到
* 参数3... 这里可以传进多个参数,现在代码设置就是:0F, 1F, 0F, 1F
* 就是从 0(完全透明)过渡到 1 (完全不透明) 再到 0(完全透明) 再到 1 (完全不透明)
* */
val objectAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
objectAnimator.duration = 4000
objectAnimator.start()
}
OK ,运行下代码,tv_animator_view这个View 确实按照上面说的展示:
从完全透明过渡到完全不透明,再到完全透明,再到完全不透明
举一反三,
我们看下 旋转(rotation)
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view2, "rotation", 0F, -90F,90F,0F)
rotationAnimator.duration = 10000
rotationAnimator.start()
记得我上一篇文章说的,旋转设置的
正数 代表 顺时针方法,
负数 代表 逆时针方法。
所以上面 运行的效果是:先从原始位置,旋转到 逆时针旋转90度,然后从当前位置 旋转到 顺时针90度方向,然后再回到原始处。
运行时的动画效果图我就不贴了。。。。麻烦。。。
我们再看一下 平移(translationX 或 translationY):
//获取当前 View X方向偏量 如果在原始位置,XY轴的偏移量都是0
val originX = tv_animator_view3.translationX
//获取 View 的宽度,返回值单位 px像素
val width = tv_animator_view3.width
/**
* 参数1 tv_animator_view3 目标View
* 参数2 translationX X轴的平移 正为往右 负数为左,单位为像素
* 参数3 这里可以传进多个参数,
* 运行结果:从原始位置 往左移动 当前View的宽度,然后再回到原始位置
* */
val translationAnimator =
ObjectAnimator.ofFloat(tv_animator_view3, "translationX", originX, width.toFloat(), originX)
translationAnimator.duration = 10000
translationAnimator.start()
运行结果就是我注释写的:从原始位置 往左移动 当前View的宽度,然后再回到原始位置
注意下方法里面的单位:
translationX X轴的平移 正为往右 负数为左,单位为像素
translationY Y轴的平移 正为往下 负数为上,单位为像素
我们再看一下 缩放(scaleX 或 scaleY):
val translationAnimator =
ObjectAnimator.ofFloat(tv_animator_view3, "scaleX", 0F, 1F)
translationAnimator.duration = 10000
translationAnimator.start()
看代码,聪明的你们都可以看出来了,就是在X轴方向,从中心点 从 0 放大到 1(原始尺寸)
如果换成 scaleY ,那就是 在纵向 进行缩放。。。。。
我们再看一下 动画循环:
val translationAnimator =
ObjectAnimator.ofFloat(tv_animator_view3, "scaleX", 0F, 1F)
translationAnimator.duration = 10000
// INFINITE是无限循环,无限循环还有:RESTART和REVERSE,具体看源码注释
translationAnimator.repeatCount = ValueAnimator.INFINITE
translationAnimator.start()
至此。属性动画最常用的四种动画(alpha、rotation、translationX和scaleY)都展示了。
但是就如最上面所提问的,方法里面的第二个参数,还可以填什么,我给你回答是:任何字符。
what??? are you kidding me? 不是,我并没有开玩笑。
Android 设计的时候,就没有限定 只能是View,可以是Object任何一个子类。
所以,你若在 ObjectAnimator.ofFloat(tv_animator_view3, "abd", 0F, 1F),传 abd,对这个View 是不会有任何动画效果的。
当然,也不至于 会carsh.....
你可以这么理解,传了一个 “scaleX” 进去,动画在进行过程中,就会不断在目标 查找,当前传进去的目标是否有setScaleX() 这个方法。
敲黑板!注意!! 是对应的 方法
如果有,就会按照进度去调用这个方法。如果没有,则不处理。
而一开始传进去的是TextView ,它有方法 setScaleX(),动画就会对它进行改变。
但是如果我传进去的是“abd”,TextView是没有方法setAbd() ,所以它不会改变任何东西。。。。也不会报错
TextView 的方法 setScaleX(),其实是在其父类View 里面的,源码是这样的:
所以是调用View 的这个setScaleX()
举一反三,聪明的你们,相比也会想到,View里面肯定会有:
setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY() 这些方法。。。。。
至于,ObjectAnimator 机制是怎样找到对应set()方法的。。。其实是这样的,ObjectAnimator是依赖当前线程 Looper,如果当前线程 没有Looper,会报错的。从ObjectAnimator .start() 跟踪到最终的 ValueAnimator.star()方法可以看到。
至于怎创建当前的 Looper,就不是本章的内容。读者自己去补习。。。。。
其实一直把源码跟到最终的 animateValue(float fraction),
里面有一段代码叫:mValues[i].calculateValue(fraction); 这段代码就是用来计算每一帧的属性值的
好的,我们看一下,属性动画是怎么获取 get() 和 set()方法的,这才是我们最关心的。。。。
get()方法,是在我们没有初始化的时候去获取的。 在 PropertyValuesHolder
通过反射获取
那么,set() 呢?其实也是在 PropertyValuesHolder 里面。因为我们创建ObjectAnimator 传进去的第二个参数,
就是保存 在 此类里面的,所以通过 PropertyValuesHolder 获取 get 和 set。
上面源码跟踪只是截取了关键部分,具体跟踪路径读者 自行去查找,,,哈哈
============【动画 中间代理】==================
好吧,我们来个Demo 来验证 属性动画的 方法 -- 属性
我们先建立一个 AnimationTemp 类
class AnimationTemp {
private val TAG = "AnimationTemp"
fun setAbcint(abcInt: Int) {
Log.d(TAG, "调用 setAbcInt() abcInt : $abcInt ")
}
fun getAbcint(): Int {
Log.d(TAG, "调用 getAbcInt() ")
return 100
}
}
然后 按照属性动画的原则,动画对象可以是任何对象,好。我们就用这个 AnimationTemp 作为动画对象
/**
* 参数1 以 AnimationTemp 作为 动画对象
* 参数2 改变 abcint 这个属性,
* 对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
* 参数3 起始值
* 参数4 结束值
* */
val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 0, 10)
ofInt.duration = 100
ofInt.start()
看下运行结果:
好了,很明显了。。。。。不断调用 setAbcint(abcInt: Int) 从0打印到10
再总结下,分析下原理吧:
1> ObjectAnimator.onInt() 创建了一个以Int 渐变的动画,动画对象是 AnimationTemp实例,需要改变的属性叫:abcint
2> 动画进行时,系统 尝试去变动画对象的属性 abcint ,那么对应
系统会在动画对象里寻找一个叫:setAbcint(abcInt: Int) 的方法,注意方法的入参类型 和 方法名的命名大小写
3> 所以在整个动画周期内,不断调用 setAbcint(abcInt: Int) 去改边目标动画的属性值
这个时候,你们可能看到,我们的getAbcint() 并没有调用,其实上面的源码已经分析到了,就是我们没有初始化值时,会调用。
好了,我们改一下代码:
创建 动画时,传入结束值, (如果你只传一个Int只,系统默认为此值是结束值)
/**
* 参数1 以 AnimationTemp 作为 动画对象
* 参数2 改变 abcint 这个属性,
* 对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
* 参数3 结束值
* */
val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 10)
ofInt.duration = 100
ofInt.start()
然后 AnimationTemp 不变
class AnimationTemp {
private val TAG = "AnimationTemp"
fun setAbcint(abcInt: Int) {
Log.d(TAG, "调用 setAbcInt() abcInt : $abcInt ")
}
fun getAbcint(): Int {
Log.d(TAG, "调用 getAbcInt() ")
return 100
}
}
看下结果:
好了,很明显。验证了源码的分析。
当没有初始化动画的初始值时,会在动画目标 里找对应属性的 get()方法,作为开始值。
这里我们返回100,作为开始值,然后结束值为10
所以从 100 打印到 10
所以,到这里,大家基本上知道属性动画的原理了吧。都明白了为什么属性动画的目标可以使任何目标!!!!
============【组合动画】==================
说完原理。最后说一下多个动画一起 进行。。组合动画。。。
AnimatorSet 就是用这个类。可以把 ValueAnimator 和 ObjectAnimator 一个或多个放进来播放
主要方法:
play() 播放单个
playSequentially() 按顺序播放,一个接着一个来
playTogether() 所有动画一起播放
after(Animator animIn) 将现有动画插入到传入(即:animIn)的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator animIn) 将现有动画插入到传入动画(即:animIn)之前执行
with(Animator anim) 将现有动画和传入(即:animIn)的动画同时执行
【Demo1----after】
好了,我们来跑一趟代码:
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.play(alphaAnimator).after(rotationAnimator)
animatorSet.start()
运行效果是:rotationAnimator 先执行, alphaAnimator在执行
【Demo2----with】
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.play(alphaAnimator).with(rotationAnimator)
animatorSet.start()
运行效果是:rotationAnimator 和 alphaAnimator 同时在执行
【Demo3----before】
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.play(alphaAnimator).before(rotationAnimator)
animatorSet.start()
运行效果是:alphaAnimator先执行,rotationAnimator后执行
【Demo4 -- after(long)】
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.play(alphaAnimator).before(rotationAnimator).after(3000)
animatorSet.start()
运行效果是:延时3秒,alphaAnimator先执行,rotationAnimator后执行
【Demo5 ---- playTogether】
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.playTogether(alphaAnimator, rotationAnimator)
animatorSet.start()
运行效果是:rotationAnimator 和 alphaAnimator 同时在执行
【Demo5 ---- playSequentially】
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.playSequentially(alphaAnimator, rotationAnimator)
animatorSet.start()
运行效果是:alphaAnimator先执行,rotationAnimator后执行
==================【动画的监听】==================
其实就是一个方法
val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
alphaAnimator.duration = 4000
val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
rotationAnimator.duration = 4000
val animatorSet = AnimatorSet()
animatorSet.addListener(object : Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator?) {
//重复
}
override fun onAnimationEnd(animation: Animator?) {
//结束
}
override fun onAnimationCancel(animation: Animator?) {
//取消
}
override fun onAnimationStart(animation: Animator?) {
//开始
}
})
animatorSet.playSequentially(alphaAnimator, rotationAnimator)
animatorSet.start()
其实,不知道信心的你们,有没有发现,属性动画没有发现有 setFillAfter() 设置动画结束后,是否恢复到初始状态。
这个嘛,我们可以通过监听动画的结束,然后再结束的时候再设置一下target的状态
public class ResetAnimatorListenerAdapter extends AnimatorListenerAdapter {
View animatorView;
public ResetAnimatorListenerAdapter(View animatorView) {
this.animatorView = animatorView;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//重置 animatorView 的状态
animatorView.setTranslationY(0);
animatorView.setAlpha(0.0f);
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
}
}
上面说了一大通 代码动态设置动画,其实还可以通过 xml 设置,具体的就不说。因为不太建议用xml,比较死不能更改。
最后说一下,动画的注意事项
1:防止 OOM,避免使用图片比较多的 帧动画。。。
2:防止内存泄漏,View退出不可见时,要停止动画
3:属性动画是在Android3.0以上使用的,低于这个版本,可以使用兼容库
4:View 动画,当移动View了以后,有时会导致View.GONE设置失效,这个时候只要调用View.clearAnimation()清除动画既可
5:动画进行时,可以开启硬件加速,提高流畅性。
以上代码亲测无问题,有问题请留言指正,谢谢