Android 3.0 (API level 11)引入了属性动画系统,它是一个完善的框架,可以用来对几乎任何对象进行动画。只需要指定要动画的对象属性,动画时长,属性值区间等,无论对像是否在屏幕中显示与否,都可以随时间改变其属性值达到动画效果。
属性动画支持以下特性:
下面两图分别展示了对一个View对像的x坐标属性执行不同属性动画时属性随时间变化的情况。动画均持续40ms,使用系统默认的10ms更新一次位置x,动画使得x从0增加到40。
可以看到x属性值随时间均匀变化。
可以看到x属性值随时间不均匀变化。
动画的本质就是“值随时间变化”这样的过程。 属性动画涉及的类型及其工作流程如下:
动画值
的过程。0到1
间的一个float数值,它是经过的时间t和动画总时间duration的比值,表示动画进度从0%到100%。比如上面案例中,t=10ms时动画时间进度为0.25f=25%=10ms/40ms;属性值变化百分比
——叫做插值分数(interpolated fraction),插值器其实就是一个函数,根据时间进度得出变化百分比。时间进度、TimeInterpolator根据时间进度得到的结果“插值分数”都是一个百分数,后者表示从startPropertyValue到endPropertyValue的差值的百分比。需要注意的是时间进度的值只能是0~1f,而interpolated fraction可以是<0或>1的,比如展示像左右摇摆最后固定在某处这样的效果。
evaluate(float fraction, T startValue, T endValue)
根据插值分数及属性的startValue和endValue来得到fraction对应的实际属性值。上面示例中的x是int类型的,它对应IntEvaluator。 针对上面的“线性变化”的动画,t=20ms时,时间进度为0.5f,线性插值器映射得到的变化百分比依然是0.5f,最终得到得属性值为(40 - 0) x 0.5f = 20。
以上就是属性动画涉及的关键类型以及它们之间的工作关系。
View动画的限制包括:
属性动画没有以上限制,可以针对任何对像的任何属性,而且真实地改变了这些属性。 属性动画系统地设计也更加健壮,可以在很高的抽象层次上自定义插值器,或者同步多个Animator等。
从框架实现上看,对view animation的支持直接贯穿了View、ViewGroup这些类型的源码,代码入侵性较大。 而属性动画可以是针对任意类型对象的,它是以组合的方式,实现上仅仅是调用目标对象的方法,几乎不对要动画的类型做要求。所以设计上看属性动画更合理些。
View动画使用上更加简单些,可以根据需求选择其中之一。
相关类型都在包android.animation下,View动画android.view.animation里面的一些interpolators也可以在属性动画中使用。
Animator
表示属性动画的抽象。它的子类有:
AnimatorSet, ValueAnimator 、 ObjectAnimator, TimeAnimator 等。
Animator实例使用Evaluator来对对像属性执行计算,其接口为:
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}
在上面“工作原理”中的描述已经很容易明白evaluate()方法的功能,其逻辑为
returnValue = startValue + fraction * (endValue - startValue);
TypeEvaluator的实现类有:
ArgbEvaluator, FloatArrayEvaluator, FloatEvaluator, IntArrayEvaluator, IntEvaluator, PointFEvaluator, RectEvaluator
A time interpolator defines how specific values in an animation are calculated as a function of time. 根据动画时间进度得到属性值的变化百分比。
接口TimeInterpolator是插值器接口:
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
参数input是时间进度百分比,getInterpolation()返回映射后的属性值变化百分比。
它的实现类有:
LinearInterpolator、DecelerateInterpolator、BounceInterpolator
等。
ValueAnimator用来在指定的startValue和endValue区间内执行动画,产生一系列动画值。
它提供了若干静态工厂方法来获得针对不同类型数据的实例:
ofInt(), ofFloat(), or ofObject().
使用方式:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.setRepeatCount(20);
// 反转REVERSE/重复RESTART/无限INFINITE
animation.setRepeatMode(ValueAnimator.INFINITE);
animation.start();
animation.cancel();
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
使用方法ofObject()对自定义类型等非基础类型进行动画。 ValueAnimator可以设置重复次数、重复模式,可以取消等。
start()方法执行后动画开始,之后会回调动画监听器的各个方法,ValueAnimator不和某个具体的对像关联(其setTarget()是空实现),它执行的动画是针对startValue、endValue这样的抽象数据进行的,所以不会自动产生对任何对像属性的影响。
使用ValueAnimator时需要结合“Animation Listeners”来获得动画执行的回掉,然后自己调用getAnimatedValue()来得到当时的动画值后自己去更新目标对像的属性值。
“Animation Listeners”的使用稍后了解。
ObjectAnimator 是ValueAnimator的子类,所以它拥有“动画时间引擎”和属性值计算的能力,同时又支持关联目标对像,这样对目标对像就可以在动画过程中自动更新其属性。而ValueAnimator则必须结合“Animation Listeners”手动更新。
ObjectAnimator的创建类似ValueAnimator,但需要目标对像和属性名参数:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
使用ObjectAnimator需要注意:
可以使用AnimatorSet组合多个ValueAnimator,可以是其它的AnimatorSet。 最终使得一些动画在另一些动画开始前或结束后执行,多个动画同时或依次执行,或其中一些动画延迟执行等。
下面是API demo中一个案例的代码片段,其功能是:
对应实现代码:
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();
通过动画监听器可以对动画过程的重要事件进行监听,获得回调。
监听动画生命周期特殊事件。
监听动画帧更新,它就一个onAnimationUpdate() 方法,在产生每一帧时被回调。
在方法onAnimationUpdate()中可以调用getAnimatedValue()获得当前的动画值,ValueAnimator的实例必须添加AnimatorUpdateListener来获得动画值序列。而ObjectAnimator在更新一些对像的某些属性——如view的background颜色时,也需要使用此接口手动执行一些逻辑。
AnimatorListenerAdapter是AnimatorListener的一个适配类,如果仅需要获得部分回调时它作为一种简化。
针对ViewGroup,在其childViews被add、remove、可见性发生变化、位置变化等事件发生时,如果希望执行一些布局相关的动画,可以使用LayoutTransition。
LayoutTransition定义了表示布局变化的若干常量,可以使用这些常量来指定关联的ValueAnimator到LayoutTransition,最后使用LayoutTransition来使得一个ViewGroup拥有布局变动时的动画效果。
常量包括:
首先要在布局文件中为ViewGroup设置属性android:animateLayoutChanges为true:
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/container"
android:animateLayoutChanges="true" />
在代码中:
// container是目标ViewGroup
ViewGroup container = (ViewGroup)findViewById(R.id.container);
final LayoutTransition transitioner = new LayoutTransition();
container.setLayoutTransition(transitioner);
transition.setAnimator(LayoutTransition.APPEARING, customAppearingAnim);
方法setAnimator(int transitionType, Animator animator)用来设置某种布局变动时执行的Animator动画。
之后当container中childViews发生变化时,对应动画就自动执行。
如果目标对象属性的数据类型就不是IntEvaluator, FloatEvaluator这些支持的,就需要自己定义TypeEvaluator的实现类来使用。
只需要重写evaluate()方法,像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);
}
}
参数fraction是根据动画时间进度以及interpolator得到的变化百分比,也就是已经是插值计算后的结果值。
根据Animator传递的动画时间进度返回动画值的变化百分比。 Animator的duration和repeats决定了动画的时间和周期行为,而Interpolator决定了动画过程 的表现。 比如LinearInterpolator:
public float getInterpolation(float input) {
return input;
}
是一个和动画时间无关的常量。自己可以根据需要提供Interpolator实现类来完成需要的动画过程。
一个Keyframe表示动画过程某个时间点上的动画值,默认的startValue、endValue就是开始结束 时的两个Keyframe。 可以为Animator指定若干Keyframe来精确控制其动画过程,而且每个Keyframe可以设置通过setInterpolator()来指定自己的插值器——上一个Keyframe执行到此Keyframe时使用。
ValueAnimator.ofFloat()、setFloatValues()等实际上都是在指定动画的关键帧。 示例代码:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf0 = Keyframe.ofFloat(0.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 animation的动画是通过其父容器来绘制动画效果,而自身属性无法变化,这样带来一些UI上的奇怪问题。Property动画则实际改变了View对象的属性,其底层原理正是框架调用view对象的setter、getter实现。如果没有那么也可以自己再onAnimationUpdate()中主动设置对象属性,并调用invalidate()通知重绘。 可以想象属性动画系统应该是通过反射来调用这些属性对应的setter/getter方法的。 为了配合新的属性动画,View类添加了一些对应其UI属性的getter/setter:
可以像这样来获得一个对View进行旋转的动画:
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
如果一次同时修改一个对象的若干属性,那么ViewPropertyAnimator提供了一种便捷的方式。 它内部使用一个Animator对象来实现功能,和ObjectAnimator表现上很接近,使用更加简单。
下面的代码实现相同的功能,展示了这点:
属性动画同样支持xml文件定义,复用性更好,且编辑简单。 为了区分之前的view animation,建议在res/animator/下放置属性动画的资源文件。
下面是一个示例:
<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>
标签对应的类型:
<animator>
<objectAnimator>
<set>
可见xml声明方式定义属性动画更加方便,阅读性好。尤其是多个动画的同时、依次执行的设置。
在代码中:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
方法setTarget()用来关联要动画的目标View,而属性名称在xml中就可以指定。