札记:Property动画
简介
Android 3.0 (API level 11)引入了属性动画系统,它是一个完善的框架,可以用来对几乎任何对象进行动画。只需要指定要动画的对象属性,动画时长,属性值区间等,无论对像是否在屏幕中显示与否,都可以随时间改变其属性值达到动画效果。
属性动画支持以下特性:
- Duration:动画持续时间,默认为300ms;
- Interpolation:插值器,随动画时间变化属性值的公式。
- Repeat Count & behavior:重复次数,重复类型(是否反转)等。
- Animator sets:动画集合,若干动画一起或依次执行。
- Frame refresh delay:帧率,默认是10ms,但最终决定与系统的运行负载和对底层时钟的响应速度。所给值仅仅是期望值。
工作原理
下面两图分别展示了对一个View对像的x坐标属性执行不同属性动画时属性随时间变化的情况。动画均持续40ms,使用系统默认的10ms更新一次位置x,动画使得x从0增加到40。
- 线性变化
可以看到x属性值随时间均匀变化。
- 非线性变化
可以看到x属性值随时间不均匀变化。
动画的本质就是“值随时间变化”这样的过程。
属性动画涉及的类型及其工作流程如下:
-
首先使用ValueAnimator来定义动画,它保存动画的开始值startPropertyValue、结束值endPropertyValue,以及动画持续时间duration;
-
当执行方法start()后,动画开始。
-
动画开启后,随着时间行进,每当到达一个帧频率(或者说刷新频率)后——比如上面的默认的是每过10ms后,会触发一次动画帧生成。
-
接下来,每次生成动画帧,进入计算
动画值
的过程。 -
计算动画时间进度(elapsed fraction):属性动画系统抽象了对动画时间的表示,我们不要去假设它和真实时间的关系。实际上时间都表示为相对某一时刻所经过的毫秒数,任何使用时间作为参数的系统只需要保证时间参考点的唯一有效即可。
属性动画使用最终的时间进度——而非具体的时间增量来表示动画的时间进展,这个时间一般称作“逻辑时间”,和View Animation是一样的。最终动画时间进度是0到1
间的一个float数值,它是经过的时间t和动画总时间duration的比值,表示动画进度从0%到100%。比如上面案例中,t=10ms时动画时间进度为0.25f=25%=10ms/40ms; -
计算属性值变化百分比:时间进度确定后,为了让动画值变化和时间进度保持某种映射关系以表现出特殊动画效果,如加速减速运动等,需要用到“插值器”——TimeInterpolator,它根据动画时间进度得到一个最终的
属性值变化百分比
——叫做插值分数(interpolated fraction),插值器其实就是一个函数,根据时间进度得出变化百分比。时间进度、TimeInterpolator根据时间进度得到的结果“插值分数”都是一个百分数,后者表示从startPropertyValue到endPropertyValue的差值的百分比。需要注意的是时间进度的值只能是0~1f,而interpolated fraction可以是<0或>1的,比如展示像左右摇摆最后固定在某处这样的效果。
-
计算属性值:得到t时刻的属性值变化百分比后,需要获得对应的实际属性值。因为不同对像的不同Property的数据类型是不一样的,属性动画中使用TypeEvaluator来抽象对目标属性的计算,它定义了方法
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。
-
改变对像属性值:得到t时刻的动画值后,框架会修改目标view对像的属性x的值。
以上就是属性动画涉及的关键类型以及它们之间的工作关系。
属性动画和View动画的区别
View动画的限制包括:
-
只能针对View对像,而且不是所有属性,如background color无法默认被动画,需要自己编写逻辑实现不支持的non-view对像及属性。
-
动画仅改变了view的绘制内容,而它的位置信息没有变化。
属性动画没有以上限制,可以针对任何对像的任何属性,而且真实地改变了这些属性。
属性动画系统地设计也更加健壮,可以在很高的抽象层次上自定义插值器,或者同步多个Animator等。
从框架实现上看,对view animation的支持直接贯穿了View、ViewGroup这些类型的源码,代码入侵性较大。
而属性动画可以是针对任意类型对象的,它是以组合的方式,实现上仅仅是调用目标对象的方法,几乎不对要动画的类型做要求。所以设计上看属性动画更合理些。
View动画使用上更加简单些,可以根据需求选择其中之一。
属性动画框架总揽
相关类型都在包android.animation下,View动画android.view.animation里面的一些interpolators也可以在属性动画中使用。
Animator
Animator
表示属性动画的抽象。它的子类有:
AnimatorSet, ValueAnimator 、 ObjectAnimator, TimeAnimator 等。
Evaluator
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
Interpolators
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
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
ObjectAnimator 是ValueAnimator的子类,所以它拥有“动画时间引擎”和属性值计算的能力,同时又支持关联目标对像,这样对目标对像就可以在动画过程中自动更新其属性。而ValueAnimator则必须结合“Animation Listeners”手动更新。
ObjectAnimator的创建类似ValueAnimator,但需要目标对像和属性名参数:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
使用ObjectAnimator需要注意:
-
动画更新的对像需要具备属性名对应的setter方法:set
()这样的。如果没有,那么可以:1. 继承并添加方法。 2. 自定义类型并封装目标类型,然后提供方法。 3. 使用ValueAnimator。 -
上面强调的是ValueAnimator的startValue和endValue,实际上可以指定1~n个值,它们作为动画过程中的若干中间值。如果仅指定一个值,那么它被作为endValue。此时startValue自动根据对像当前目标属性生成,这时就需要对应属性的getter了。
-
getter和setter必须是同样的数据类型。因为对应的startValue和endValue是一样的。
-
虽然ObjectAnimator自动更新目标对像属性,但如果需要正常显示的话,一些属性不会自动重绘view对像,此时就需要手动调用对像的invalidate()来完成——通过动画监听器的onAnimationUpdate() 回调,后面会了解。如对一个view对像背景Drawable 对像修改颜色;而那些直接操作View的方法 setAlpha() 、setTranslationX() 等本身会自动重绘view。
使用AnimatorSet
可以使用AnimatorSet组合多个ValueAnimator,可以是其它的AnimatorSet。
最终使得一些动画在另一些动画开始前或结束后执行,多个动画同时或依次执行,或其中一些动画延迟执行等。
下面是API demo中一个案例的代码片段,其功能是:
- Plays bounceAnim.
- Plays squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2 at the same time.
- Plays bounceBackAnim.
- Plays fadeAnim.
对应实现代码:
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();
Animation Listeners
通过动画监听器可以对动画过程的重要事件进行监听,获得回调。
Animator.AnimatorListener
监听动画生命周期特殊事件。
- onAnimationStart() - Called when the animation starts.
- onAnimationEnd() - Called when the animation ends.
- onAnimationRepeat() - Called when the animation repeats itself.
- onAnimationCancel() - Called when the animation is canceled. A cancelled * animation also calls onAnimationEnd(), regardless of how they were ended.
ValueAnimator.AnimatorUpdateListener
监听动画帧更新,它就一个onAnimationUpdate() 方法,在产生每一帧时被回调。
在方法onAnimationUpdate()中可以调用getAnimatedValue()获得当前的动画值,ValueAnimator的实例必须添加AnimatorUpdateListener来获得动画值序列。而ObjectAnimator在更新一些对像的某些属性——如view的background颜色时,也需要使用此接口手动执行一些逻辑。
AnimatorListenerAdapter是AnimatorListener的一个适配类,如果仅需要获得部分回调时它作为一种简化。
LayoutTransition
针对ViewGroup,在其childViews被add、remove、可见性发生变化、位置变化等事件发生时,如果希望执行一些布局相关的动画,可以使用LayoutTransition。
LayoutTransition定义了表示布局变化的若干常量,可以使用这些常量来指定关联的ValueAnimator到LayoutTransition,最后使用LayoutTransition来使得一个ViewGroup拥有布局变动时的动画效果。
常量包括:
- APPEARING - A flag indicating the animation that runs on items that are appearing in the container.
- CHANGE_APPEARING - A flag indicating the animation that runs on items that are changing due to a new item appearing in the container.
- DISAPPEARING - A flag indicating the animation that runs on items that are disappearing from the container.
- CHANGE_DISAPPEARING - A flag indicating the animation that runs on items that are changing due to an item disappearing from the container.
首先要在布局文件中为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发生变化时,对应动画就自动执行。
使用TypeEvaluator
如果目标对象属性的数据类型就不是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得到的变化百分比,也就是已经是插值计算后的结果值。
使用Interpolator
根据Animator传递的动画时间进度返回动画值的变化百分比。
Animator的duration和repeats决定了动画的时间和周期行为,而Interpolator决定了动画过程
的表现。
比如LinearInterpolator:
public float getInterpolation(float input) {
return input;
}
是一个和动画时间无关的常量。自己可以根据需要提供Interpolator实现类来完成需要的动画过程。
指定Keyframes
一个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);
对Views执行动画
view animation的动画是通过其父容器来绘制动画效果,而自身属性无法变化,这样带来一些UI上的奇怪问题。Property动画则实际改变了View对象的属性,其底层原理正是框架调用view对象的setter、getter实现。如果没有那么也可以自己再onAnimationUpdate()中主动设置对象属性,并调用invalidate()通知重绘。
可以想象属性动画系统应该是通过反射来调用这些属性对应的setter/getter方法的。
为了配合新的属性动画,View类添加了一些对应其UI属性的getter/setter:
- translationX and translationY: These properties control where the View is located as a delta from its left and top coordinates which are set by its layout container.
- rotation, rotationX, and rotationY: These properties control the rotation in 2D (rotation property) and 3D around the pivot point.
- scaleX and scaleY: These properties control the 2D scaling of a View around its pivot point.
- pivotX and pivotY: These properties control the location of the pivot point, around which the rotation and scaling transforms occur. By default, the pivot point is located at the center of the object.
- x and y: These are simple utility properties to describe the final location of the View in its container, as a sum of the left and top values and translationX and translationY values.
- alpha: Represents the alpha transparency on the View. This value is 1 (opaque) by default, with a value of 0 representing full transparency (not visible).
可以像这样来获得一个对View进行旋转的动画:
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
ViewPropertyAnimator
如果一次同时修改一个对象的若干属性,那么ViewPropertyAnimator提供了一种便捷的方式。
它内部使用一个Animator对象来实现功能,和ObjectAnimator表现上很接近,使用更加简单。
下面的代码实现相同的功能,展示了这点:
- ObjectAnimator
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
- One ObjectAnimator
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
- ViewPropertyAnimator
myView.animate().x(50f).y(100f);
使用xml来定义动画
属性动画同样支持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>
标签对应的类型:
- ValueAnimator -
<animator>
- ObjectAnimator -
<objectAnimator>
- AnimatorSet -
<set>
可见xml声明方式定义属性动画更加方便,阅读性好。尤其是多个动画的同时、依次执行的设置。
在代码中:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
方法setTarget()用来关联要动画的目标View,而属性名称在xml中就可以指定。
补充
- 动画时间进度
如果动画是循环的,那么要知道,时间进度始终是一次动画过程中的百分比,即duration。
资料
- api docs:/docs/guide/topics/graphics/prop-animation.html
(本文使用Atom编写)