Android API Guide for Animation and Graphics(二)—— 动画与图形(属性动画)

属性动画

属性动画系统是一个允许你让几乎所有对象都能有动画的鲁棒性架构。你可以定义一个随时间轴改变对象的属性,且无需对象是否已绘制好在屏幕上的动画。属性动画在指定时间内改变对象的属性的值。为了赋予对象动画,你需要指定一个对象的目标属性,比如对象在屏幕上的位置,以及它动画的时长,还有动画期间属性变化的值。

属性动画系统定义了如下的特性:

  • Duration:你可以指定动画的播放时长,默认是300ms。
  • Time interpolation:时间插值器,你可以指定如何计算属性的值作为动画当前已过时间的函数。
  • Repeat count and behavior:你可以指定动画结束之后是否重复、重复的次数、是否要反向播放动画、正/反播放动画的顺序是否重复。
  • Animator sets:你可以创建动画集在指定延迟时间后同时播放或者按顺序播放。
  • Frame refresh delay:你可以指定动画多久进行刷新,默认是10ms。但是你的应用程序动画的刷新速率取决于你系统的繁忙程度以及系统运行的快慢。

属性动画的工作原理

首先,让我们通过一个简单的例子来了解属性动画的工作原理。图1假设在屏幕的水平位置上有一个x属性在进行动画的对象。动画时长为40ms以及运行了40px的距离。由于属性动画的默认刷新速率为10ms,所以对象每10ms水平移动10px。当到了40ms的时候,动画就结束了,对象也停留在了水平位置40px处。这个动画例子是通过一个匀速插值器来实现的。

这里写图片描述
图1.线程动画

你也可以通过指定属性动画的插值器为非线性的。图2表明了动画在开始时加速,在将结束时减速。但对象仍然在40ms中移动了40px。开始时,动画加速到中点,然后从中点减速到动画结束。正如图所示,动画开始和结束移动的距离比在中间点移动的要少。

这里写图片描述
图2.非线性动画

让我们仔细的看看属性动画的重要组件是如何计算如上文的动画。图3描述ValueAnimator是如何一步步运行的。

这里写图片描述
图3.动画是如何计算的。

ValueAnimator对象通过跟踪动画的时间,例如动画已过了多长的时间,以及当前属性的值。

ValueAnimator封装了定义动画插值规律的时间插值器(TimeInterpolator),以及定义了如何计算属性的值的类型估值器(TypeEvaluator)。如图2中,使用了AccelerateDecelerateIntercepolator的时间插值器和IntEvaluator的类型估值器。

要启动一个动画,需要创建一个ValueAnimator并赋予其起始和结束的属性值。当你调用start()的时候动画就开启了。在整个动画期间,ValueAnimator根据动画播放的总时长和已经过去的时间从0到1计算已过去时间的分数。已过时间的分数表示动画完成的百分比。0意味着0%,1意味着100%。比如图1,t = 10ms的时候,过去时间分数为0.25,因为总时长为40ms。

当ValueAnimator计算完过去时间分数的时候,它就会调用当前设置的TimeIntercepolator来计算插值的分数。插值分数将其插值到当前设置的时间插值器上去。比如图2,因为动画是缓慢的加速,插值分数约为0.15,比图1的0.25小。在图1中的插值分数总是0.25。

当插值分数计算完之后,ValueAnimator调用合适的类型估值器(TypeEvaluator)并根据这个插值分数来计算动画属性当前的值,开始的值,结束的值。比如图2,t = 10ms时插值分数为0.15,所以这个时刻属性的值就为0.15X(40 - 0) = 6

属性动画与视图动画的区别

视图动画系统只提供作用于View对象的动画功能,所以如果你想将视图动画使用到非View对象上,你不得不自己实现代码。视图动画实际还有其他限制,就是它只暴露了几种动画,比如缩放,旋转,但是它没有提供像改变背景色值这样的动画接口。

视图动画系统另一个缺点就是它只在需要绘制视图的地方进行修改,而不是View本身。比如,如果你动画一个按钮在屏幕移动,该按钮正常绘制了,但事实上按钮的点击位置还在原来的位置上,你只能通过自己实现一些代码来处理这种情况。

如果是使用属性动画,这些约束就不存在了,你可以将属性动画作用于任何对象的任何属性上(包括View和非View的对象),而且它是修改对象自身。属性动画系统在执行动画方面也是比较鲁棒的。在高水平级别应用上,你可以将动画赋予到你想要的属性上,如颜色,位置,或者大小,而且在自定义动画方面,你可以自定义插值器和多个同步运行的动画。

然而,视图动画只需花费较少的时间以及需要较少的代码就可以实现。如果视图动画能实现你所需,或者你现有的代码已经实现了你所需,就没有必要去另外用属性动画了。如果有这种需求时,也可以使用两种动画系统来处理不同的情况。


API Overview

你可以在android.animation包中找到大部分属性动画系统的API。因为视图动画系统已经定义了很多插值器在android.view.animation包中,你也可以使用这些插值器。下面的列表是属性动画系统主要成员。

Animator类提供了基本的构造函数来创建动画。通常你不需要直接使用此类,因为继承它只是实现了最基本的动画功能。以下是Animator的子类扩展

Table1.Animators

1.1 ValueAnimator

计算赋予动画的属性的值的主要时间引擎。它包含了计算属性动画的值、获取每个动画的时间细节、获取关于是否动画重复,是否有监听器、是否接收更新事件的信息以及自定义估值器的所有核心功能。属性动画需要两个步骤:一就是计算属性动画的值,并在赋予动画的对象上设置这些值。ValueAnimator没有实现第二部分的细节,所以你必须在你业务代码中实现监听并更新属性的值。

1.2 ObjectAnimaor

ValueAnimator的子类 ,可以作用于对象和对象的属性上。这个类会在动画期间计算出一个新的属性值之后更新属性。大多数情况会使用这个类,因为它使你程序上的目标对象的属性值更容易改变。然而,有时候你得直接使用ValueAnimator,因为ObjectAnimator有一些约束,比如需要在目标对象上指定一个可访问的方法(后面有使用ObjectAnimator的注意点)。

1.3 AnimatorSet

提供一种将动画组在一起以使它们彼此相关运行的机制。你可以设置这些动画同时播放,顺序播放,或者指定某个延迟时间后播放。


估值器定义属性动画系统如何根据指定的属性计算值。他们携带着Animator类提供的时间参数以及动画的起始值和结束值,并根据这些参数计算动画的属性值。属性动画系统提供了如下的估值器:

Table2.Evaluator

2.1 IntEvaluator

默认计算整型属性值的估值器。

2.2 FloatEvaluator

默认计算浮点型属性值的估值器。

2.3 ArgbEvaluator

默认计算十六进制色值属性的估值器

2.4 TypeEvaluator

允许你自定义类型的估值器接口。如果你指定的动画属性的值不是int,float或者color,你必须通过实现类型估值器来指定如何计算对象属性的值。如果你想让默认的类型估计器有所不同,你也可以指定自定义类型估值器为int,float或者color。


时间插值器定义动画中指定的值如何计算并作为时间的函数。比如,你可以指定整个动画过程是线性移动的,这意味着这个动画过程是匀速移动的,或者你可以指定动画使用非线性时间插值器,打个比方,开始动画时加速,结束动画时减速。表格3描述android.view.animation包中的插值器。如果没有一个适合你的插值器类型,你可以通过实现TimeInterpolator接口定义自己的插值器。

Table3.Interpolators

3.1 AccelerateDecelerateInterpolator

起始和结束的速率是缓慢变化的,中间速率是加速的插值器。

3.2 AccelerateInterpolator

缓慢开始然后加速的插值器。

3.3 AnticipateInterpolator

An interpolator whose change starts backward then flings forward.

3.3 AnticipateOvershootInterpolator

An interpolator whose change starts backward, flings forward and overshoots the target value, then finally goes back to the final value.

3.4 BounceInterpolator

在结束时反弹的插值器。

3.5 CycleInterpolator

动画指定重复次数周期插值器。

3.6 DecelerateInterpolator

快速开始然后减速的插值器。

3.7 LinearInterpolator

速率匀速变化的插值器。

3.8 OvershootInterpolator

An interpolator whose change flings forward and overshoots the last value then comes back.

3.9 TimeInterpolator

允许你自定义插值器的接口。


Animating with ValueAnimator

ValueAnimator类通过指定int,float或者color,并在动画期间设置这些属性的值。你可以通过调用ValueAnimator的工厂方法:ofInt(),ofFloat(),ofObject(),如下:

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();

这段代码,当start()方法被调用的时候,ValueAnimator在1000ms内从0到1计算动画的值。
你也可以像下面一样指定一个自定义类型来播放动画:

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

这段代码中,当start()方法调用时,ValueAnimato就根据MyTypeEvaluator提供的逻辑计算startProperty和endPropertyValue之间的动画值,持续时间为1000ms。

然而,前面的代码片段对对象是没有任何实际的影响的。因为ValueAnimator不能直接操作一个对象或者对象的属性,更多的是获取计算的值来操作修改对象。你需要实现ValueAnimator的监听器来适当的处理动画期间的变化,比如动画帧的更新。当实现好监听器的时候,你可以通过调用getAnimatedValue()获取属性动画计算的值.


Animating with ObjectAnimator

ObjectAnimator是ValueAnimator的子类(在前面的部分中讨论),它结合了定时引擎和ValueAnimator的值计算以及对对象的属性进行动画的处理能力。它可以让任何对象实现属性动画更加简便,因为它自动帮你实现ValueAnimator.AnimatorUpdateListener的接口,你无需再自己去实现。

ObjectAnimator的使用示例很像ValueAnimator,但是你要指定作用的对象以及对象跟随动画播放的时间改变的属性(用字符串形式表示,如下代码的“alpha”)

ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();

想要ObjectAnimator正确的更新属性值,你必须遵循以下几点:

  • 赋予属性动画的对象必须有一个如set()的setter方法。因为ObjectAnimator在动画期间自动更新属性的值,它必须有能够访问该属性的setter方法。比如,如果属性名是foo,你就需要提供一个setFoo()方法。当不存在setter方法时,你有三种解决方法可以选择:

    • 在有权限的条件下,添加setter方法到类中
    • 使用有setter方法接收计算的值并转发到原始对象的包装类中。
    • 使用ValueAnimator替代
  • 如果OjectAnimator的工厂方法中values…参数只传入一个指定的值,这个值将被视作动画结束时的值。因此,赋予属性动画的对象必须提供一个格式如get的getter方法来获取动画属性的起始值。比如,属性名为foo,则需要提供一个getFoo()方法。

  • 对象暴露的getter方法(在需要提供的情况下)和setter方法在动画期间处理的类型,必须与指定在ObjectAnimator的起始值和结束值的类型一样。比如,如果你用下面的参数值构造ObjectAnimator(结束值为1f,浮点类型),你必须提供targetObject.setPropName(float)和targetObject.getPropName(float)

ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 取决于动画的属性和对象的类型,你可能需要在View中调用invalidate()方法并根据属性动画更新的值来强制屏幕重绘View本身。这个情况需要在onAnimationUpdate()方法中回调。比如,赋予Drawable对象色值属性动画后,它只会在对象重绘自身的时候才会去更新屏幕自身的状态。所有View的属性设置,比如setAlpha(),setTranslationX()都能正确的invalidate,所以当有新的值传入这些设置器的时候,你不需要再调用invalidate View。

通过AnimatorSet组合多个动画

在许多情况,你播放的动画都是依赖于另一个动画的开始或者结束。Android系统允许你将多个动画绑定到AnimatorSet中,你可以指定是否同时播放动画,顺序播放,或者某个延迟时间后开始。你也可以在AnimatorSet中再内嵌一个AnimatorSet动画集。

下面通过Bouncing Balls的动画播放顺序来演示AnimatorSet的使用方式:
1. 播放 bounceAnim.
2. 同时播放squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2
3. 播放bounceBackAnim
4. 播放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();

Animatin Listeners

你可以在动画期间设置的监听器:

  • Animator.AnimatorListener
    • onAnimationStart() - 动画开始时调用
    • onAnimationEnd() - 动画结束时调用
    • onAnimationRepeat() - 动画重复时调用
    • onAnimationCancel() - 动画被取消时调用,且不管动画是否已经结束也会调用onAnimationEnd()
  • ValueAnimator.AnimatorUpdateListener
    • onAnimationUpdate() – 动画期间每一帧都会调用一次(默认10ms刷新一次),监听这个事情可以获取ValueAnimator在动画期间计算的值。想要获取这个值,需要调用通过ValueAnimator对象的getAnimatedValue()方法并传到这个事件中去。

如果你不想实现Animator.AnimatorListener所有的方法的话,你可以通过继承AnimatorListenerAdapter来代替实现Animator.AnimatorListener接口,AnimatorListenerAdapter类提供了几个空的方法实现给你选择并重写。

如下实例:Bouncing Balls Demo创建AnimatorListenerAdapter只是想回调onAnimationEnd()方法。

ValueAnimator 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的布局上的变化

属性动画系统提供了对ViewGroup对象进行动画变更的功能,以及对View对象更容易进行动画变更的方式。
你可以在ViewGroup中使用LayoutTransition类实现的动画对布局进行改变。ViewGroup也可以给里面View的显示与隐藏及添加与删除添加动画。这意味着当添加View或者删除View的时候,可以通过动画设置它们新的位置。你可以在LayoutTransition对象中通过调用setAnimator()定义动画,并将该动画传入赋值到如下LayoutTransition的一个常量中:

  • APPEARING - items出现在容器中时运行动画的标记。
  • CHANGE_APPEARING - items由于一个新的item的出现在容器中而变更时运行动画的标识。
  • DISAPPEARING - items退出容器中时运行动画的标记。
  • CHANGE_DISAPPEARING - items由于一个item退出容器而变更时运行的动画标识。
    你可以为这四种事件类型自定义动画来定制你布局过度的样式,或者直接使用默认的动画。

LayoutAnimations的示例教你如何给定义布局过渡动画,并设置到想要赋予动画效果的View对象上。
PS:LayoutAnimation这里没有过多介绍,Google走起


使用类型估值器

如果你想对一个Android系统中没有的类型进行动画,你可以通过实现TypeEvaluator接口创建自己的类型估值器。IntEvaluator,FloatEvaluator、ArgbEvaluator类型估值器支持Andorid系统已知几种类型:int、float、color。

实现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);
    }
}

Note:当ValueAnimator或者ObjectAnimator动画在运行时,它计算出当前已过时间的分数(0到1)然后根据你当前的插值器类型转换插值数。这个插值数就是你类型估值器接收的参数(如代码中的float fraction),所以当动画的值在计算的时候,你不需要再考虑这个插值数的计算。


使用插值器

插值器定义动画如何根据指定的值计算出作为时间函数的值。例如,你可以指定整个动画期间线性播放,这意味着整个动画过程是匀速的,或者你可以指定动画为非线性移动,比如在开始时加速或在结束时加速。

在动画系统中插值器接收一个代表动画已过时间的分数。插值器根据动画所提供的类型来修改这个分数(不理解可以看下文几种插值器的getIntercepolation的实现)。Android系统的android.view.animation.package包提供了一系列常用的插值器,如果没有一个适合你的的话,你也可以通过实现TimeIntercepolator接口创建自己的插值器。

举个例子,比较 AccelerateDecelerateInterpolator和 LinearInterpolator是如何计算插值的分数的。已过时间分数对LinearInterpolator没有影响。AccelerateDecelerateInterpolator在动画载入时加速,在动画载出时减速。下面的方法是这些插值的计算逻辑:

AccelerateDecelerateInterpolator

public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

public float getInterpolation(float input) {
    return input;
}

指定关键帧

一个关键帧由时间/值 对组成,它定义动画指定的时间出现指定的状态。每个关键帧也可以有它自己的插值器来控制动画前一个关键帧与当前关键帧的动画之间那段间隔的动画效果。

初始化一个关键帧对象,你必须使用一个工厂方法,ofInt(),ofFloat(),ofObject()来获取合适的关键帧类型。然后通过调用工厂方法ofKeyframe()来获取PropertyValueHolder对象。一旦你获取该对象,你就可以通过该对象获取一个动画,并赋予到你要进行动画效果的对象上。下面的代码片段是Keyframe的使用示例:

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对象动画,它的优点比View Animation的多。视图动画系统通过改变View的绘制方式来切换View对象。这只能在每个View的容器内处理,因为View本身没有可操作的属性。这将导致虽然视图在变换,但是View本身是没有改变的。在Android 3.0,新的属性以及相应的getter和setter方法来消除这个缺点。

属性动画系统可以通过真正的改变View对象的属性来实现视图动画效果。另外,Views也会随着属性值的改变自动的调用invalidate()方法来刷新。View中可以用于属性动画的新属性是:

  • translationX and translationY:这些属性控制视图作为从其布局容器设置的左侧和顶部坐标的增量的位置
  • rotation, rotationX, and rotationY:这些属性控制2D旋转(rotation)和围绕枢纽点进行的3D旋转
  • scaleX and scaleY:控制View围绕枢纽点2D的缩放
  • pivotX and pivotY:动画围绕的枢纽点,默认是对象的中心点。
  • x and y:用于在其容器中描述视图的最终位置,其中X作为左边距+translationX的和,Y表示上边距+translationY的和。
  • alpha: 控制透明度

使用示例:

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

使用ViewPropertyAnimator进行动画

ViewPropertyAnimator提供一个简单的方法来实现View的几种属性动画。它更像是ObjectAnimator,因为它是通过修改View本身的属性值,但是通过它实现属性动画效率更高。另外,ViewPropertyAnimator的代码也非常简洁跟易读。下面通过同时改变View的x,y属性代码片段用来展示使用多个ObjectAnimator对象、单独的ObjectAnimator对象以及ViewPropertyAnimator的区别:
Multiple ObjectAnimator objects

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资源文件中声明动画。通过在XML中定义动画,你可以更容易的修改动画,且在多个Activity中重复使用。

为了从遗留下来的视图动画中区分出属性动画资源文件,从Android 3.1开始,你需要保存XML文件在res/animator目录下。

下面是在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资源文件填充到代码中的AnimatorSet对象中,然后在开始执行动画之前将所有的动画设置到目标对象上。通过调用setTarget()可以很方便的把动画集的所有动画设置到目标对象上。下面是示例代码:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);
set.start();

原文链接:https://developer.android.google.cn/guide/topics/graphics/prop-animation.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值