Android动画-Property Animation
Android动画-Property Animation
前言
Property Animation是通过改变对象的属性值来创建动画的机制。尽管View Animation提供了Tween Animation 和 Frame Animation这些动画能解决我们开发过程中常用的大多数动画特效,然后View Animation还是有很多的限制,比如Tween Animation 仅仅支持平移、缩放等这些操作,Frame Animation 是直接对多张图的逐帧播放,这些都收到很大的限制,然而在Android 3.0之后, google 提出了 Property Animation,顾名思义,我们可以使用Property Animation 对对象的属性创建动画,比如View 对象的背景颜色、水平方向垂直方向的位置、以及View提供的其他属性,可以说Property Animation 几乎无所不能,借下来这篇文章我将带领大家了解Property Animation 到底是如何实现的。
Property Animation 常见特性
- Duration:你能指定动画的持续时间,默认为300ms.
- Time interpolation: 指定插值器,TimeInterpolator是所有插值器的接口,用来定义变化率。可以指定如何将属性值作为动画当前运行时间的函数进行计算。
- Repeat count and behavior:可以定义重复次数以及重复的模式。
- Animator sets:动画集
- Frame refresh delay:可以指定刷新动画的帧的频率,默认为10ms刷新一次。但是实际上刷新的时间视系统当前时间是否繁忙为准。
Property Animation是如何工作的
下面让我们先来看一下Property是如何进行运动的:
图片1-匀速运动
上图一展示的是一个我们假设的对象在水平方向上移动了40个px,花费了40ms.以10ms刷新一次动画,四次10ms都运动了10px,采用的是线性匀速运动,以相同的速率进行移动。
img
上图二展示了非线形运动的图示。以水平方向开始和结束位置相同,同样时间内采用不用的速率进行移动。从图中我们可以看出,以小x=20中间点为例,该动画从起点加速到中间点,然后从中间点再以减速运动移动。
下面我们再来讨论下 property Animation 系统是如何通过列出的重要组件进行计算的
[外链图片转存失败(img-JooCXZ48-1563449081754)(https://developer.android.com/images/animation/valueanimator.png)]
上图三列出了一些property Animation的重要组件:
- ValueAnimator:用于跟踪动画计时,比如现在动画跑了多久、当前的属性值。
- TimeInterpolator:用来定义插值器,用来计算插值分数,其子类为AccelerateInterpolator、AccelerateDecelerateInterpolator等。
- TypeEvaluator:用来定义如何计算当前动画值,evaluate()传入当前插值分数,开始值和结束值。
- ValueAnimator.Animator.UpdateListener:监听属性的改变
比如,在图二中使用的TimeInterpolator是AccelerateDecelerateInterpolator,使用的TypeEvaluator是系统定义好的IntEvaluator。
当我们创建一个Property Animation时,创建一个ValueAnimatior对象,提供一个开始和结束值、动画持续时间。当调用ValueAnimatior.start()动画开始,在整个动画过程中ValueAnimator计算过去时分数,过去时分数介于0-1之间、是通过经过的时间/动画持续的时间来计算的。分数因子类比百分比数,0等于0%,1等价于100%。比如图一在10ms的时候分数因子为10/40=.25.
ValueAnimator计算出过去时分数后,它会调用当前设置的TimeInterpolator来计算插值分数。插值分数是通过输入过去时分数这个变量,通过当前设置的TimeInterpolator公式进行计算的。比如图二中,因为物体会缓慢加速,所以在10ms内插值分数为.15少于.25,而图一是以一种匀速运动运动一直运动,插值分数不会改变。
当插值分数计算出来后,ValueAnimator会调用合适的TypeEvaluator进行计算当前动画的属性值,计算变量为插值分数、开始值、结束值。比如图二中,在10ms的时候插值分数为.15,所以当前动画的属性值为.15*(40-0).
完成整个计算动画的流程后,我们进行整理:
- ValueAnimator计算出过去时/总时间的过去时分数
- ValueAnimator根据当前设置的TimeInterpolator计算出插值分数
- ValueAnimator使用合适的ValueAnimator根据插值分数、开始值、结束值计算当前的动画的属性值
Property Animation 和 View Animation的不同点
- View Animation 仅仅支持开放的几个API,比如对View Object 缩放、平移之类,而不能以背景色为改变动画
- View Animation仅能对View Object设置动画 ,Property Animation可以为非View Object设置动画
- View Animation仅修改绘制View的位置,而不是实际View本身,比如点击的时候还是仅能点击移动前的位置才有响应,而Property Animation 修改的直接是View 的实际位置,就没有这些问题
- 因为View Animation 代码更简单、开发时效更快。所以在我们实际开发中,如果View Animation如果已经能满足我们的要求,那么我们就不必要使用Property Animation.
ValueAnimator
PropertyAnimation创建动画是通过两个步骤来走的:
- 第一步是计算每个动画时间更改的值
- 第二步是给目标对象设置改变属性值
从Property Animation是如何工作的一章我们了解到,ValueAnimator完成了第一步,而第二步需要我们编写逻辑来实现,使用ValueAnimator的流程如下:
- 使用ValueAnimator.ofInt()、ValueAnimator.ofArgb()、ValueAnimator.ofFloat()工厂方法创建ValueAnimtor对象
- 设置常用配置信息,比如动画持续时间(duration)、重复次数(repeatCount)、重复模式(repeat)、插值器(interpolator)、动画值更新监听器(updateListener)等
- 在动画值更新监听器获得当前动画值,修改属性。
下面我们列出代码示例:
ValueAnimator.ofFloat(0f, 100f).apply {
//设置动画持续时间
duration = 4000
//设置重复次数
repeatCount=10
//重复模式
repeatMode=ValueAnimator.REVERSE
// 设置加速插值器
interpolator=AccelerateInterpolator()
addUpdateListener { updateAnimation ->
//改变目标的属性值
iv_image.translationX = (updateAnimation.animatedValue as Float)
}
start()
}
代码中,展示了一个简单以加速运动在水平方向从0平移100的动画特效,持续4000模式,重复10次。
ObjectAnimator
ObjectAnimator是ValueAnimation的子类,它对ValueAnimation进一步封装,主要封装了对目标对象设置改变的属性值,以使得创建属性动画的代码更加简洁、开发速度更快速。
还是之前的平移动画效果,我们看看使用ObjectAnmator是如何实现上面的代码的:
ObjectAnimator.ofFloat(iv_image, "translationX", 0f, 100f).apply {
duration = 4000
repeatCount=10
//设置重复次数
repeatMode=ValueAnimator.REVERSE
//设置加速插值器
interpolator=AccelerateInterpolator()
start()
}
代码和之前的有点类似,也是使用工厂方法创建ObjectAnimator传入目标对象,想要修改的属性。ObjectAnimator注重于帮助我们设置改变的属性,不再需要编写addUpdateListener中的代码,极大的增加了代码的简洁性。
在使用ObjectAnimator我们必须确保一些前置条件,否则属性动画将无法设置属性:
- 确保目标动画有**set()**格式的方法
- 给目标对像添加setXXX方法
- 假如原始目标对象无法修改,那么我们创建一个包裹类,创建**set()**转发到真正需要修改的目标对象
- 如果以上两种条件都不允许,那么我们只能创建ValueAnimator
- 如果仅仅在工厂方法ofFloat(…)中传入一个值,默认该值为结束值,因为我们还需要取到一个初始值,所以需要确保有一个get 的方法。比如Foo属性值,需要一个getFoo()的方法
- 设置动画属性的set和get方法的类型,必须与指定ObjectAnimator的起始值和结束值一致。比如ObjectAnimator.ofFloat(targetObject, “propName”, 1f)限定了set和get属性方法的类型为Float.
- 根据一些属性和对象,有时候需要显式的在调用View对象的invalidate()。类似View对象中setTranslationX()、setScaleX()、setScaleY()这些方法内部调用了invalidate()所以不必显式调用。
AnimatorSet
有时候我们需要对动画进行组合,AnimatorSet就能把几个单个动画特效整合成一个组合动画,并且可以控制单个动画的执行顺序。
AnimatorSet是Animator的子类,控制者动画集合,根据Animator提供的API控制顺序:
- play():创建依赖项的动画,以此动画为原点。比如play(a1) ,如果之后带上before和after以a1作为坐标系
- with():同时执行,比如play(a1).with(a2) a1和a2同时执行
- before():在…之前,比如play(a1).before(a2) a1在a2之前执行
- after():在…之后,比如play(a1).after(a2) a1在a2之后执行
代码示例:
val translateAnimation = ObjectAnimator.ofFloat(iv_image, "translationX", 0f, 100f).apply {
duration = 1000
}
val translateAnimation1 = ObjectAnimator.ofFloat(iv_image, "translationX", 0f, 200f).apply {
duration = 1000
}
val translateAnimation2 = ObjectAnimator.ofFloat(iv_image, "translationX", 0f, 300f).apply {
duration = 1000
}
val scaleXAnimator = ObjectAnimator.ofFloat(iv_image, "scaleX", 0f, 1f).apply {
duration = 1000
}
// 创建AnimationSet
val boundAnimbouncer = AnimatorSet().apply {
// translateAnimation在scaleXAnimator之前执行
play(translateAnimation).before(scaleXAnimator)
// translateAnimation在translateAnimation1之后执行
play(translateAnimation).after(translateAnimation1)
// translateAnimation和translateAnimation1一起执行
play(translateAnimation).with(translateAnimation2)
}
// 创建透明度改变动画
val faceAnim = ObjectAnimator.ofFloat(iv_image, "alpha", 0f, 1f).apply {
duration = 5000
}
AnimatorSet().apply {
//faceAnim在boundAnimbouncer之前执行
play(faceAnim).before(boundAnimbouncer)
start()
}
在使用AnimatorSet之前,我们需要创建单独的动画效果:
translateAnimation平移100、translateAnimation1平移200、translateAnimation2平移300、scaleXAnimator水平方向缩放,faceAnim透明度改变动画。
在我们设置的执行顺序时,真实的动画效果顺序应当以如下顺序执行:
- faceAnim执行
- translateAnimation1执行
- translateAnimation、translateAnimation2一起执行
- scaleXAnimator执行
动画监听状态
你可以使用**Animator.AnimatorListener()**监听在动画过程中一些重要的事件:
- onAnimationStart():当动画开始的时候回调
- onAnimationEnd():当动画结束的时候回调
- onAnimationRepeat():当动画重复的时候回调
- onAnimationCancel():当动画被取消的时候回调,不管是否结束动画,相应的也会回调onAnimationEnd()方法
你可以使用**ValueAnimator.AnimatorUpdateListener()**监听动画的每一帧。在动画过程中是使用ValueAnimator对象来计算的。为了使用计算值,你可以通过ValueAnimator.getAnimatedValue()获得当前动画计算的值。更多的详情可以查看-ValueAnimator一章。
如果你不想实现所有的Animator.AnimatorListener()接口方法,你也可以继承AnimatorListenerAdapter替代**Animator.AnimatorListener()接口。AnimatorListenerAdapter提供了实现了Animator.AnimatorListener()**接口但是提供了一些空实现的方法。
比如,我们可以仅仅实现一个onAnimationEnd()方法的代码示例:
ValueAnimator.ofFloat(0f, 100f).apply {
//设置动画持续时间
duration = 4000
repeatCount = 10
//设置重复次数
repeatMode = ValueAnimator.REVERSE
//设置加速插值器
interpolator = AccelerateInterpolator()
addUpdateListener { updateAnimation ->
//改变目标的属性值
iv_image.translationX = (updateAnimation.animatedValue as Float)
}
//添加一个重要事件的监听
addListener(object:AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
iv_image.visibility=View.GONE
}
})
start()
}
自定义TypeEvaluator
如果安卓系统无法识别你当前想要转换的动画类型值,你可以通过实现TypeEvaluator实现**evaluate()**来自定义动画计算值。安卓系统为int,float和color这些类型提供了支持类IntEvaluator,FloatEvaluator,ArgbEvaluator.
源码FloatEvaluator
在开始自定义TypeEvaluator之前,我们先来看看源码中定义的Float类型的FloatEvaluator:
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
FloatEvaluator的实现很简单,它就是根据fraction插值分数,计算当前动画的值。
自定义TypeEvaluator流程
为了更好的理解Property Animation 是如何工作了,我们编写一个抛物线的动画特效:
先编写一个PointFEvaluator自定义TypeEvaluator类:
public class PointFEvaluator implements TypeEvaluator<PointF> {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
PointF pointF=new PointF();
pointF.x=200 * fraction * 3;
pointF.y=0.5f * 200 * (fraction * 3) * (fraction * 3);
return pointF;
}
}
evaluate()方法中,x轴以x=常量*变量因子改变,y轴以y=常量*变量因子的平方计算,这是抛物线的物理公式。
然后为ValueAnimator设置自定义的TypeEvaluator,并在updateListener监听器中设置属性值。
ValueAnimator().apply {
//设置动画持续时间
duration = 4000
//设置目标值
setObjectValues(PointF(0f,0f))
//设置自定义的 TypeEvalutor
setEvaluator(PointFEvaluator())
//设置加速插值器
interpolator = LinearInterpolator()
addUpdateListener { updateAnimation ->
//改变目标的属性值
val pointF=updateAnimation.animatedValue as PointF
iv_image.x=pointF.x
iv_image.y=pointF.y
}
start()
}