前言
属性动画系统是一个强大的框架,可以用来为添加几乎各种动画。你可以通过按时间改变对象的属性来定义任何动画,无论这个对象是否被绘制在屏幕内。一个属性动画在特定的时间范围内改变一种属性(也就是一个对象的某个成员变量的值)。想让某个对象具有动画效果,需要指定要应用动画的对象的属性,比如对象在屏幕中的位置,以及动画需要持续的时间还有动画的初始值和结束值。
属性动画系统允许定义以下一些动画的特性:
- 持续时间:可以指定动画的持续时间,默认时长为300ms。
- 时间插值器:可以指定属性值随时间变化的函数来来实现动画效果。
- 重复次数和行为:可以指定动画是否需要重复执行以及重复的次数,还可以指定动画知否需要进行倒放。设置动画倒放会让动画顺序进行后再倒序进行,知道重复指定次数为止。
- 动画集合:可以把多个动画按一定逻辑组合成一个动画集合一起进行,也可以逐个进行或者在延迟特定事件后进行。
- 帧刷新延迟:可以指定刷新动画帧的频率,默认值是每10ms刷新一次。但是应用可以刷新帧的速率最终取决于系统的繁忙程度以及系统对计时器的响应速度。
属性动画如何工作
图1. 线性动画的例子
让我们来看看属性动画系统用来计算动画数值的一些关键类。图3描述了这些类之间的关系:
图3. 动画属性值的计算过程
ValueAnimator对话保持对于动画时间的跟踪,比如动画已经持续了多长时间已经当前动画对应的属性值是多少。
ValueAnimator封装了一个TimeInterpolator(用来定义动画使用的插值器)、一个TypeEvaluator(用来定义如何计算产生动画的属性值)。对图2来说,TimeInterpolator使用的是AccelerateDecelerateInterpolator,TypeEvaluator使用的是IntEvaluator。
要启动一个动画,需要创建一个ValueAnimator然后设置动画初始值和结束值以及动画的持续时间即可,调用start()方法将启动动画。在整个动画过程中,ValueAnimator会根据动画总的时长和已经经过的时间计算动画完成的比例(比例为一个小数,在0到1之间)。这个完成比例代表着动画进行时间的百分比,0代表0%,1代表100%。例如对于图1来说,10ms时的动画完成比例就是25%,因为动画总的时长是40ms。
当ValueAnimator计算完动画的完成比例之后,它会调用预设的TimeInterpolator,来计算插值的比例。一个插值比例是把动画完成比例依据时间插值器进行了转换。例如对图2来说,因为动画是逐渐加速的,在10ms时插值比例(比如0.15)将小于动画完成比例(0.25)。而对于图1,插值比例和动画完成比例是相同的。
当插值比例计算出来之后,ValueAnimator会调用合适的TypeEvaluator依据插值比例、初始值和结束值来计算执行此动画的对应属性的值。例如对于图2,10ms时的插值比例是0.15,执行动画的属性是物体的x坐标,初始值为0,结束值为40,那么对应的属性值为0.15 x (40 - 0) = 6。
属性动画与视图动画的区别
API概览
- ValueAnimator:属性动画主要的时间引擎,用于计算执行动画所需要的属性值。计算动画数值的全部核心功能、动画随时间变化的全部细节、动画的重复控制、接受动画更新事件的监听器等都包含在这个类中。属性动画包括两个主要的方面:计算执行动画所需要的数值以及把这些数值设置到对象相应的属性上。ValueAnimator并不负责后一方面的工作,所以需要监听ValueAnimator对动画数值的更新然后自己实现为对象属性设置动画数值的逻辑。
- ObjectAnimator:ValueAnimator的子类,允许设置一个动画目标对象和目标对象用于执行动画的属性。该类在计算属性动画数值之后会更新相应属性的值。大多数情况下使用的都是ObjectAnimator,用它来处理目标对象的属性动画会变得更为简单。当然ObjectAnimatory也存在一些限制,比如需要目标对象具有符合要求的特定的访问方法。当ObjectAnimator不能实现需求时,可以使用ValueAnimator。
- AnimatorSet:提供一种管理动画组的机制来让多个动画依据一定关系执行。可以设置多个动画一起执行或者逐个执行或者延迟一定时间后执行。
- IntEvaluator:默认的计算int型属性的数值计算器
- FloatEvaluator:默认的计算float型属性的数值计算器
- ArgbEvaluator:默认的计算颜色属性的十六进制数值的计算器
- TypeEvaluator:用于自定义Evaluator的接口。如果执行动画的对象的属性不是int、float或者颜色类型,就需要实现TypeEvaluator来自定义计算属性动画数值的方式。对 于int、float和color类型的属性,同样也可以通过TypeEvaluator来自定义数值计算器。
- AccelerateDecelorateInterpolator:开始段逐渐加速,结束段逐渐减速,中段速度最快。
- AccelerateInterpolator:全程加速,结束段速度最快。
- AnticipateInterpolator:开始先反向动作然后在执行动画。
- AnticipateOvershootInterpolator:开始先反向动作,到达结束值之后越过结束值在返回到结束值。
- BounceInterpolator:结束时数值震荡效果。
- Cycleinterpolator:动画按自定次数重复。
- DecelerateInterpolator:全程减速,结束段速度最慢。
- LinearInterpolator:动画速率全程恒定。
- OvershootInterpolator:动画执行到结束值之后越过结束值在返回到结束值。‘
- TimeInterpolator:允许自定义Interpolator的接口。
使用ValueAnimator执行动画
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
以上代码中,ValueAnimator在startPropertyValue和endPropertyValue中使用MyTypeEvaluator指定的逻辑来计算动画数值。
使用ObjectAnimator执行动画
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
- 执行动画的属性一定要有setter方法,并且要按照驼峰命名方式命名为set<propertyName>()的形式。因为ObjectAnimator会自动更新属性值,所以必须要有能访问属性值的setter方法。例如,如果属性名是"foo",就需要有setFoo()方法来设置属性值。如果setter方法不存在,还有三种处理方式:
- 如果有权限的话,那么为对象的类型增加setter方法。
- 使用一个有权限修改对象属性的包装类,通过包装类的setter方法来获取数值然后把数值传递给对象。
- 使用ValueAnimator来代替。
- 如果对ObjectAnimator的工厂方法的参数values...只指定了一个数值,那么该数值会被认为是结束值。因此,执行动画的对象属性必须有一个用于获取初始值的getter方法。getter方法的方法名必须是get<propertyName>()的形式。例如,如果属性名是"foo",那么需要有getFoo()方法。
- getter方法(如果需要)和setter方法的参数类型一定要与创建ObjectAnimator对象时传递的参数类型相同。例如,如果是通过如下代码创建的ObjectAnimator:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
那么必须要有targetObject.setPropName(float)和targetObject.getPropName(float)方法。 - 取决于将动画应用于何种对象或属性上,可能需要调用view的invalidate()方法来强制使用新的动画数值来进行视图的重绘。可以在onAnimationUpdate()回调中进行该操作。例如,改变一个Drawable对象的颜色只有在该对象重绘之后才能在屏幕上看到更新的效果。View中的各属性的setter方法,比如setAlpha()和setTranslationX()都会适时调用invalidate()方法,所以当使用这些方法为View属性赋值的时候并不需要主动调用invalidate()方法。
使用AnimatorSet编排多个动画
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();
动画监听器
- Animator.AnimatorListener
- onAnimationStart() —— 动画开始时调用
- onAnimationEnd() —— 动画结束时调用
- onAnimationRepeat() —— 动画重复时执行
- onAnimationCancel() —— 当动画被取消时调用。一个被取消的动画依然会调用onAnimationEnd()方法,无论它是否已经结束了。
- ValueAnimator.AnimatorUpdateListener
- onAnimationUpdate() —— 动画的每帧都会调用。使用该监听器来使用ValueAnimator在动画过程中计算出的动画数值。要使用该数值,可以通过传入该方法的ValueAnimator对象的getAnimatedValue()方法。ValueAnimator必须实现getAnimatiedValue()方法。
取决于执行动画的对象和属性,可能需要调用View的invalidate()方法强制重绘对象。
- onAnimationUpdate() —— 动画的每帧都会调用。使用该监听器来使用ValueAnimator在动画过程中计算出的动画数值。要使用该数值,可以通过传入该方法的ValueAnimator对象的getAnimatedValue()方法。ValueAnimator必须实现getAnimatiedValue()方法。
ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}
为ViewGroup的布局变化添加动画效果
- APPREARING:表明是ViewGroup中的子View出现时使用的动画。
- CHANGE_APPREAING:表明是ViewGroup中由于子View出现,其他View发生改变时使用的动画。
- DISAPPEARING:表明是ViewGroup中的子View消失时使用的动画。
- CHANGE_DISAPPEARING:表明是ViewGroup中由于子View消失,其他View发生改变时使用的动画。
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
该属性设置为true将会自动为添加或删除的子View添加默认动画,同时也会为ViewGroup中因布局改变而影响的其他View添加动画。
使用TypeEvaluator(数值计算器)
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);
}
}
注意:当ValueAnimator(或ObjectAnimator)运行时,会计算一个当前动画已经完成的比例(0到1之间),然后会根据使用的插值器来把这个比例做一个转换。这个经过插值器转换之后的动画完成比例就是TypeEvaluator接收的fraction参数,所以在使用TypeEvaluator计算动画数值的时候不需要在考虑插值器的问题。
使用Interpolators(插值器)
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
public float getInterpolation(float input) {
return input;
}
下表展示了一个持续1000ms的动画的一些时间点的近似值:
动画已完成时间(ms) | 插值器转换后的动画完成比例(线性) | 插值器转换后的动画完成比例(加速再减速) |
0 | 0 | 0 |
200 | 0.2 | 0.1 |
400 | 0.4 | 0.345 |
600 | 0.6 | 0.8 |
800 | 0.8 | 0.9 |
1000 | 1 | 1 |
指定关键帧
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);
为View添加动画
- translationX和translationY:这两个属性用来控制View绘制的偏移距离,偏移距离是相对于View的父容器的左上角来计算的。
- rotation,rotationX和rotationY:rotation属性控制View的2D旋转,rotationX和rotationY控制根据旋转轴的3D旋转。
- scaleX和scaleY:这两个属性控制View根据其中枢轴(pivot)的2D缩放。
- pivotX和pivotY:这两个属性用来控制中轴点位置。缩放动画和旋转动画都将依据中轴点进行。中轴点的默认位置是View的中心位置。
- x和y:用来控制View在其父容器中的最终位置,是由translationX和translationY累加得到的。
- alpha:用来控制View的透明度。默认值为1(不透明),0代表完全透明。
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
使用ViewPropertyAnimator来执行动画
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
myView.animate().x(50f).y(100f);
在XML文件中声明动画
- ValueAnimator —— <animator>
- ObjectAnimator —— <objectAnimator>
- AnimatorSet —— <set>
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
想要运行这个动画,需要从XML文件中加载动画然后为动画设置目标对象,最后启动动画。调用setTarget()方法可以为动画组及其中所有的子动画设置目标对象,如以下代码所示:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();