属性动画系统是一个具有鲁棒性的框架,允许你几乎让一切都动起来。你能够定义一个动画来随着时间改变任何对象的任何属性,无视该对象是否是画在在屏幕上的。属性动画在指定的时间内改变属性值(某个对象的某个属性)。为了让目标动起来,需要特别指明所要运动的目标的属性,例如目标在屏幕上的位置,运动的时间长短和运动的值的范围。
属性动画系统允许定义以下属性来用于动画。
详细看一下属性动画系统的重要组件,下图说明了主要类的工作方式。
ValueAnimator
类存储了动画的时间轨迹,例如执行时间,当前正在执行的动画的属性值等。TimeInterpolator
(定义了动画的插值)和TypeEvaluator
(定义如何计算将要变化的属性的值)封装到内部。ValueAnimator
对象,并设置想要改变的属性和开始、结束的值以及执行时间,之后调用start()
方法。ValueAnimator
对象会根据当初设定的执行时间和已经执行的时间,用0到1之间的值来标示当前执行到预设动画的哪个阶段,相当于一个完成度的百分比,叫做elapsed fraction
。ValueAnimator
执行到一个预设好的时间点时(例如执行到0.25时,速度增加),会调用那个预设时间点对应的TimeInterpolator
对象,来计算interpolated fraction
值。interpolated fraction
值通过考虑当前设定好的时间插值,来映射elapsed fraction
值成为一个新的fraction
值。例如,在变加速直线运动,40ms时间内,先缓慢加速,然后减速的运动中,四分之一时间点的interpolated fraction
值为0.15,而elapsed fraction
值为0.25;同样在匀速直线运动中,40ms,在10ms,也就是elapsed fraction
值为0.25时,interpolated fraction
的值也是0.25。interpolated fraction
的值计算完毕后,ValueAnimator
对象会调用合适的TypeEvaluator
,根据interpolated fraction
的值,从开始值到结束值,来计算正在运动的目标的相关属性值。还是之前的例子,40ms的duration,在10ms时,interpolated fraction
的值为0.15,那么在10ms的速度就应该是0.15*(40-0) = 6。 绝大多数的api都可以在android.animation
找到,在android.view.animation
已经定义了许多可以使用的插值。下面的内容是对于属性动画主要的组件做介绍。
ValueAnimator
属性动画系统中主要的用来计算属性值变化的计时器,拥有所有的计算功能的核心方法,来计算动画值,为每个动画效果记录时间和是否重复播放、接收更新事件的监听器等相关信息,也可以保持用来计算的自定义类型的集合。使得目标动起来主要由两部分的任务,一是计算属性的实时变化值,二是将这些结果值赋予要进行运动的目标的属性。
ValueAnimator
并不执行第二部分的任务,所以必须来对数值的更新作出监听,并用自己的逻辑来将更新值赋予目标属性。
ObjectAnimator
ValueAnimator
的子类,允许设置目标对象和对象的属性。这个类在计算出动画的更新值后相应的会将这个值更新到属性上。由于更新目标对象的属性值这个过程更加容易,所以ObjectAnimator
的使用会更加频繁。然而由于ObjectAnimator
要求目标对象需要拥有获取属性的特定set、get方法,所以有时ValueAnimator
也是有发挥之地的。
AnimatorSet
提供了一个组合动画的机制,使得多个动画过程之间可以相关管理。可以使得多个动画同时发生、顺序发生或者延迟特定时间发生。
动画器告诉属性动画系统对于给定的属性如何计算其变化值。由Animator
这个类来提供定时数据,基于这个数据来计算动画起始和结束的属性值。属性动画系统提供了以下的属性计算器(evaluators)。
IntEvaluator
用来计算int值类型属性的默认计算器。
FloatEvaluator
同上,计算float类型
ArgbEvaluator
同上,计算颜色值,16进制
TypeEvaluator
允许创建自己的计算器的接口。如果需要计算的属性类型不是整型或者浮点型或者颜色值,就必须要实现该接口来指定如何计算对象的属性值。如果计算整型、浮点型和颜色值的计算方式和默认的计算器不同,同样也可以使用这个接口。
时间插值器(time interpolator
)定义了动画过程中计算属性值的时间函数。例如,在整个动画过程中指定一个线性的动画,意味着动画在执行时间内是均匀发生的,又或者定义一个非线性动画,在开始加速在结束时减速。下面介绍了在android.view.animation
中的插值器。如果没有一个能满足需求,可以实现TimeInterpolator
这个接口。
AccelerateDecelerateInterpolator
该插值器起始阶段和结束阶段加速缓慢,中间阶段是加速状态。变化曲线下图
AccelerateInterpolator
全程有一个正向的加速度,变化曲线下图,其中加速度的快慢是由参数fractor决定的,也就是x的指数。
AnticipateInterpolator
回荡秋千插值器,想象为一个荡秋千的过程(此时秋千已经在比较上面的位置了,一放手就可以荡下来)。你开始用力推向更上面,然后秋千终将荡回下面。参数tension值就好比推力的大小,默认为2,是x的指数。变化曲线下图
AnticipateOvershootInterpolator
一个插值器它开始向上推,然后向下荡,荡过最低线。然后再回到最低线。变化曲线如图
BounceInterpolator
弹跳插值器,这个插值器的插值在后面呈弹跳状态。变化曲线如图
CycleInterpolator
正弦周期插值器,变化曲线如图
DecelerateInterpolator
负向加速度插值器,起初有一个快速度的初速度,在负向的加速度作用下减速,当参数fractor为1.0f。它减速的轨迹曲线为1-(1-x)^2变化曲线如图
LinearInterpolator
匀速插值器(线性插值器),输出和输入变化一致,没什么好说的。
OvershootInterpolator
以一个较大的速度减速接近最大值,达到最大值后,减小。当tension为默认值2时,曲线图如下
TimeInterpolator
这个是自定义插值器需要实现的接口。源代码如下 package android.animation;
/**
* 时间插值器定义了一个动画的变化率。
* 这让动画让非线性的移动轨迹,例如加速和减速。
*
* 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 一个0到1.0表示动画当前点的值,0表示开头。1表示结尾
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 插值。它的值可以大于1来超出目标值,也小于0来空破底线。
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);
}
ValueAnimator
VauleAnimator
类在动画集合执行期间,动态计算不同类型,例如整形、浮点型、颜色值等的参数。通过调用ofInt()
,ofFloat()
,ofObject()
方法来获得一个ValueAnimator
对象。
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
在上面的代码中,当调用start()
方法时,ValueAnimator
会在0到1000ms之间不断计算动画所需的相关值。
也可以自定义一个类型值来进行计算,看下面的代码
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
ValueAnimator
对象会在开始动画的1000ms之间,不断的计算相关属性值(值的变化范围在startPropertyValue和endPropertyValue之间)。
ValueAnimator xValue = ValueAnimator.ofInt(stratValue, endValue);
xValue.setDuration(1000);
xValue.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int x = (Integer) animation.getAnimatedValue();
//为属性赋值
}
});
xValue.setInterpolator(new LinearInterpolator());
xValue.start();
但是需要注意的是,前面的两个代码片段,实际上没有对目标对象起到实际的效果,在之前也说过,ValueAnimator
不会直接的将计算结果赋予相应的属性值。可以通过实现一个在ValueAnimator
之内的监听器接口来将计算结果赋予实际的属性。在接口内,可以通过调用getAnimatedValue()
方法来获取每一帧刷新后的属性值。
ObjectAnimator
ObjectAnimator
类是ValueAnimator
的子类,将定时器和属性值的计算的功能进行了封装,可以对目标对象的有名字的属性进行动画计算和赋值。这个类更加容易来实现动画效果,同时也由于其自动的更新属性值,所以不再需要实现ValueAnimator.AnimatorUpdateListener
接口。实例化一个ObjectAnimator
对象和ValueAnimator
是相似的,但是同样也需要指定动画目标和需要修改的属性名称(字符串名称)和属性值的变化范围。
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
ObjectAnimator
正确的使用方式如下
set()
,由于ObjectAnimator
类会自动的在执行动画期间更新属性值,所以必须能够有获得相应属性的set方法。例如,属性名称是foo
,对应的set方法名称应该是setFoo()
。如果没有对应的set方法,解决途径有三个:1.如果有权限,在目标类中添加对应的set方法;2.使用一个有权限去改变并且能够通过set方法接收合法set参数的包装类,并且把这个包装类指向原本的对象;3.使用ValueAnimator
。ObjectAnimator
类的工厂方法中的values...
参数位置上只添加了一个参数,那么这个值会被当做动画结束时的属性值,因此,目标属性应当拥有get方法,这个get方法用来获取动画起始时候的属性值,格式为get()
,命名方法同set方法。ObjectAnimator
类中指定的起始值结束值的类型一致。例如,如果像这样来初始化一个动画对象ObjectAnimator.ofFloat(targetObject, "propName", 1f)
,必须拥有targetObject.setPropName(float)
和targetObject.getPropName(float)
方法。invalidate()
方法来迫使视图使用新的属性值来重画自身。这一步骤将在onAnimationUpdate()
方法中被调用。例如,对一个Drawable
对象的颜色属性设置动画效果,只有在这个对象重画自身的时候才能在屏幕上体现出来。视图中所有类似于d setTranslationX()
和setAlpha()
的set方法都会适时的重绘视图。所以在调用这些方法时,不需要使用者自己调用。ofFloat(Object target, String propertyName, float... values) 第1个参数为目标对象;第2个参数为属性名,这里要求目标对象要有“set属性名()”的方法。后面是可变参数,表明属性目标值,一个参数表明是终止值(对象要有get属性方法),可变参数的个数为2时,表明第一个是起始值,第二个是终止值;更多个参数时,动画属性值会逐个经过这些值
AnimatorSet
来组合动画,设计复杂效果 在很多情况下,一个完整的动画可能需要一个动画效果在另一个动画效果执行之前或者之后再执行。Android系统允许将多个动画效果绑定,置于AnimatorSet
中,对于这些动画设置特定次序,同时、顺寻或者以一定延时开始。同样,AnimatorSet
也可以相互嵌套。下面的示例代码来自于Bouncing Ball
,以一定顺序的动画效果组合完整动画。
演示顺序:
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();
可以实现动画监听器来在动画执行期间监听重要的事件并作出反应。 Animatior.AnimatorListener
onAnimationStart()
动画开始时被调用。onAnimationEnd()
动画结束时被调用。onAnimationRepeat()
动画重复时被调用。onAnimationCancle
当动画被取消时调用,方法onAnimationEnd()
也会在动画取消时调用,无论是否已经结束。 ValueAnimator.AnimatorUpdateListener
onAnimationUpdate()
每帧都会被调用,通过这个方法,调用getAnimatedValue()
可以获取ValueAnimator
实时计算的属性值,用以设置相应的属性。如果使用ValueAnimator
来实现动画,这个方法是必须要实现的。invalidate()
来让目标重绘自身。但是控件中有相应setter的属性,只需要简单调用setter即可,不需要调用invalidate()
。 如果不需要实现Animatior.AnimatorListener
接口的所有方法,也可以通过继承AnimatorListenerAdapter
类来达到同样的目的。这个类实现了所有的方法(这些方法都是空方法),可以选择性的重写某些方法。例如在API的demoBouncing Balls
里,为了onAnimationEnd()
回调,创建了一个AnimatorListenerAdapter
对象。
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
提供了同样操作简单的途径来实现动画效果。
使用LayoutTransition
([trænˈzɪʃn])这个类来为布局变化添加效果。在ViewGroup
内的子view可以再添加、移除、调用setVisibility()
方法设置是否可见时伴随出现和消失的动画效果。至于在视图容器中已经存在的试图,则可以在有新视图添加到容器中或者移除某个视图,伴随动画移动到新的位置。通过调用setAnimator()
方法,将下列效果添加入一个LayoutTransition
对象,并且传入一个Animator
对象和一个LayoutTransition
常量,来达到上述效果。
亦可以以自定义动画来取代上述四种默认效果的动画。
在API demo中,有相关的示例代码,展示了如何定义LayoutTransition
和设置想要拥有出入动画的视图。LayoutAnimationsByDefault
和对应的布局文件layout_animations_by_default.xml
展示了如何在xml文件中使用默认的布局动画。需要做的只是设置一下android:animateLayoutchanges
属性的值为true
,自动的,容器内所有添加、移除的视图都会拥有相应的动画效果。
代码如下
public class LayoutAnimationsByDefault extends Activity {
private int numButtons = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_animations_by_default);
final GridLayout gridContainer = (GridLayout) findViewById(R.id.gridContainer);
Button addButton = (Button) findViewById(R.id.addNewButton);
addButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button newButton = new Button(LayoutAnimationsByDefault.this);
newButton.setText(String.valueOf(numButtons++));
newButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
gridContainer.removeView(v);
}
});
gridContainer.addView(newButton, Math.min(1, gridContainer.getChildCount()));
}
});
}
}
如果在设计动画时,涉及到值类型是自定义类型的或者是基本类型的组合,可以通过实现TypeEvaluator
接口来创建自己的计算器。目前支持的属性值类型有整型(IntEvaluator
),单精度浮点型(FloatEvaluator
)或者颜色值(ArgbEvaluator
)。
在TypeEvaluator
接口中只有一个需要实现的方法evaluate()
方法,允许所使用的animator为动画目标对象的属性值在动画执行过程中返回合适的值。下面是FloatEvaluator
的实现,需要注意的是fraction
这个参数是由插值器计算出来传入的:
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之间,elapsed fraction),之后根据所选用的插值器,将这个值映射为另外一个值( interpolated fraction ),这个值也是TypeEvaluator
参数fraction
所接收到的值,所以当在TypeEvaluator
中计算时,不需要考虑插值器。
插值器作为一个时间函数,定义了时间与属性值之间的映射关系。例如,明确一个插值器在整个动画过程中都是线性的,意味着动画的变化过程是平稳的。
插值器在动画系统中接收来自Animator代表执行时间在动画总时间比例的参数,然后根据自身所要达到的效果来修改这个值。android系统提供了一些常用的插值器,如果仍不满足要求,则可以通过实现TimeInterPolator
这个接口来创建自己的插值器。
至于不同的默认插值器根据不同的时间值又有怎么样的变化,看上面的相关变化曲线。
KeyFrame
类可设置多个“时间点-值”的映射关系,可以在动画执行过程中在特定的时间定义特定的状态。每一个关键帧对象也可以有自己的插值器来控制该关键帧和下一个关键帧之间的这段时间内动画的行为。
实例化一个KeyFrame
对象,必须调用ofInt()
、ofFloat()
、ofObject()
这些工厂方法来获取合适类型的关键帧对象。之后调用ofKeyframe()
工厂方法来获取一个ProperValuesHolder
对象,一旦拥有了这个对象,就可以通过传入这个ProperValuesHolder
对象和动画目标来获取一个animator,见如下代码:
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);
属性动画系统提供了优化的视图对象动画,并且和视图动画相比有一些优势。视图动画系统通过改变视图对象绘图的方式来达到效果,由于视图本身没有修改的权限,所以这是在视图容器中处理的,也导致了视图显示发生改变,但是视图本身没有改变的结果。在android 3.0,新的特性和增添了属性对应的setter、getter方法,来逐渐淘汰这一缺陷。
属性动画系统可以通过修改目标真实的属性值来达到屏幕上动起来的目的。此外,视图的属性无论何时被修改,都会自动调用invalidate()
方法来重绘自身,这些属性值是:
translationX
和translationY
,相对于初始位置的偏移量,左负右正,上负下正。rotation
,rotationX
,rotationY
,控制旋转的参数。scaleX
,scaleY
,控制横纵方向缩放的参数。pivotX
,pivotY
,表示缩放的中轴点XY坐标,pivot point默认为对象中心位置。x
,y
,距离父容器左上角的坐标。alph
,透明度,1表示不透明,0表示透明。 上面的这些属性都可以使用ObjectAnimator
来使用。
ViewPropertyAnimator
类提供了一种简便的方法来让一个view的多种属性在一个动画过程中并行改变。同时改变多个属性的值,但是更加高效,它的行为很像ObjectAnimator
。此外使用ViewPropertyAnimator
的代码也更加精炼和易读。下面的代码片段显示了使用ObjectAnimator
和ViewPropertyAnimator
的异同。
使用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();
使用ViewPropertyAnimator
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
插值问题的提法是:假定区间[a,b]上的实值函数f(x)在该区间上 n+1个互不相同点x0,x1……xn 处的值是f (x0),……f(xn),要求估算f(x)在[a,b]中某点x的值。基本思路是,找到一个函数P(x),在x0,x1……xn 的节点上与f(x)函数值相同(有时,甚至一阶导数值也相同),用P(x)的值作为函数f(x*)的近似。
.view.getTranslationX计算的是该view的偏移量。初始值为0,向左偏移值为负,向右偏移值为正。view.getX相当于该view距离父容器左边缘的距离,等于getLeft+getTranslationX。
Property Animation
android动画(一)Interpolator
缓冲动画函数