欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Property Animation(属性动画)

程序员文章站 2022-06-28 14:25:46
...

原文:Android Developers——Property Animation

属性动画

前言

属性动画系统是一个非常健壮的框架,可以用它来制作任何形式的动画。你可以在一段时间内更改任何一个对象的属性,无论它是否显示在屏幕上。属性动画在一个给定的时间段内更改目标属性的值。为了给对象制作动画,你需要指定你想要驱动的属性,确定动画时长、以及属性的值变化的范围。
属性动画系统允许你定义动画的以下几个特征:

  • 持续时间(duration):可以指定动画播放的时间,默认是300ms;
  • 时间插值器(time interpolation):可以确定如何计算属性在某个时刻的值;
  • 是否重复以及重复次数(repeat count and behaviour):可以确定动画是否循环播放以及循环播放的次数,也可以确定是否要倒放动画;
  • 帧数(frame refresh delay):可以确定多久刷新动画显示,默认值是10ms。这点需要结合系统繁忙程度决定。

属性动画是如何工作的

首先通过一个例子来说明动画是如何工作的。下图展示了一个假想的对象,它的X属性(用于确定它在屏幕上的水平方位)受动画操纵。动画的持续时间为40ms,总的移动距离是40像素。每过默认的刷新间隔10ms,对象就在水平方向上移动10像素的距离。在动画结束时,对象的水平方位就移动了40像素。这个例子使用了线性插值器,因而对象的移动速率是恒定的。
Property Animation(属性动画)
也可以让动画使用非线性插值器。下图展示了一个假想的对象,它在动画开始时加速,在动画结束时减速。对象同样是移动40像素,但不是匀速的。开始时,动画加速到一个中间点,然后开始减速直到动画结束。
Property Animation(属性动画)
让我们仔细地看一看属性动画系统的各个组成部分是如何计算出上述的结果的。下图展示了各个类之间是如何协作的:
Property Animation(属性动画)
ValueAnimator对象追踪动画的与时间有关的信息,比如动画已经运行了多久,当前属性值是多少等等。
ValueAnimator包含一个TimeInterpolator类,定义了动画的插值计算方方法,以及一个TypeEvaluator类,定义了如何计算属性值。例如,图2的TimeInterpolator是AccelerateDecelerateInterpolator,TypeEvaluator是IntEvaluator。
要开始一个动画,首先创建一个ValueAnimator对象,告诉它你想要驱动的属性的开始值与结束值。当你调用start()方法,动画就开始了。在动画的整个过程中,ValueAnimator根据动画的持续时间以及当前已经播放的时长计算一个时间比例因子(0~1),这个比例因子表明了动画已经完成的时间百分比。例如,在图1中,当t=10ms时比例因子应为.25,因为总的持续时间是40ms。
当ValueAnimator计算完时间比例因子后,它调用当前设置的TimeInterpolator重新计算一个插值比例因子,这个插值比例因子代表的是动画已经完成的部分的百分比。例如,在图2中,当t=10ms时,比例因子是.15。在图1中,它会始终和时间比例因子相同。当插值比例因子计算完毕后,ValueAnimator调用TypeEvaluator来计算当前属性值,根据插值比例因子、开始值和结束值。图2中,在t=10ms时插值比例因子是.15,因此属性值为0+0.15*(40-0)=6。

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

视图动画系统只能操作View对象,因此如果你想为非View的对象制作动画,你只能够自己编写代码。视图动画系统还有一个局限性,就是它只能操作View对象一部分的属性,例如平移、旋转等。
视图动画的另外一个问题是它只改变View绘制到屏幕上的方式,而不是View本身。比如,如果你通过动画让一个按钮横穿屏幕,虽然看上去是这样,但是按钮的实际位置没变,你按不到它。
利用属性动画系统,这几点约束都没有了,你可以操纵任何对象的任何属性,并且这些改动是真正有效的。
不过,视图动画的优势在于制作方便,代码量小。如果视图动画能够满足需求,那么就没有必要使用属性动画。

API概览

你可以在android.animation中找到属性动画系统绝大部分的API,还可以使用android.view.animation中由视图动画定义的那些时间插值器。下面的表格描述了属性动画系统的主要构件:

Animator

Animator类提供了动画的基础结构框架。一般来说,你不会直接使用这个类,因为这个类只提供了很少的功能,需要使用者扩充。下面的类是Animator的子类:
- ValueAnimator:属性动画的主要计时引擎,用于计算需要被动画驱动的属性值。它拥有关于计算动画属性值的所有核心功能,并且包含了动画的细节信息,包括动画是否重复、动画的事件监听器、解析自定义数据类型的方法等。动画的实现主要分成两步:计算属性值以及设置对象的相应属性值。但是,ValueAnimator不负责第二步,你必须手动监听ValueAnimator计算出的属性值变化,并根据你的需要对对象进行修改。
- ObjectAnimator:ValueAnimator的子类,允许你设置一个目标对象以及对象需要被动画驱动的属性。这个类会根据计算出来的值更新对象的属性值。由于ObjectAnimator使用起来非常方便,在需要实现动画时,你应当首先考虑使用它。不过,ObjectAnimator也存在着一些限制,例如需要目标对象提供特定名称的访问器。如果绕不开这些限制,则应使用ValueAnimator。
- AnimatorSet:提供一种将多个动画组合起来的方法。你可以让它们同时播放、依次播放或是延迟一定时间播放。

Evaluator

Evaluators用来告诉属性动画系统如何计算一个给定的属性值。它们获取Animator提供的计时数据、属性的开始值以及结束值,并根据这些来计算出各个时间点的属性值。属性动画系统提供了以下几种Evaluator:

  • IntEvaluator:用于计算int类型的属性值;
  • FloatEvaluator:用于计算float类型的属性值;
  • ArgbEvaluator:用于计算用十六进制数表示的颜色属性值;
  • TypeEvaluator:一个用于创建自定义Evaluator的接口。如果你所要计算的属性值不属于以上任何一种类型,你就必须实现这个接口,并且确定如何计算这种类型的属性值。当然,如果想产生不同于默认行为的效果,你也可以为以上几种属性创建一个不同于默认实现的Evaluator。

Interpolator

Interpolator定义了属性值变化速率的改变趋势。如果把动画比作一个正在移动的物体,那么你可以让它保持匀速移动,也可以让它的速度呈非线性变化,比如在开始时加速,在结尾时减速。通过实现TimeInterpolator接口,可以创建自己的Interpolator。下面是一些系统提供的Interpolator实现:

  • AccelerateDecelerateInterpolator
  • AccelerateInterpolator
  • AnticipateInterpolator
  • AnticipateOvershootInterpolator
  • BounceInterpolator
  • CycleInterpolator
  • DecelerateInterpolator
  • LinearInterpolator
  • OvershootInterpolator

译者注:关于这些插值器的实际效果,建议动手制作一个简单的平移动画,并设置不同的插值器来观察效果。

使用ValueAnimator制作动画

ValueAnimator类允许你设定一系列的属性值,并让它们在动画过程中持续变化。你可以使用ValueAnimator的工厂方法获取ValueAnimator实例:

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

在这段代码中,ValueAnimator会在start()方法调用后,根据起始值0、结束值100、持续时间1000毫秒这几个条件计算属性值。
也可以通过以下方法使用自定义的数据类型:

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

在这段代码中,在start()方法调用后,ValueAnimator利用MyTypeEvaluator提供的计算逻辑,计算了1000ms的持续时间内属性值从startPropertyValue到endPropertyValue的变化过程。
你可以通过为ValueAnimator对象添加AnimatorUpdateListener来使用计算出的属性值:

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

使用ObjectAnimator制作动画

ObjectAnimator是ValueAnimator的子类(前面也提到过),它在ValueAnimator提供的计时引擎以及数值计算结合在一起,用于控制一个目标对象的指定属性。这个特点使得利用ObjectAnimator控制对象变得非常容易,因为你不需要再去实现ValueAnimator.AnimatorUpdateListener就能够实时更新对象的属性。
ObjectAnimator的使用和ValueAnimator类似,区别在于你需要指定一个对象以及它的一项属性(通过String)。示例如下:

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

想要ObjectAnimator能够正确地更新属性,你需要做以下事情:

  • 要通过动画操纵的属性必须有一个特定格式的setter方法(使用驼峰命名法),如setPropertyName()。这是因为ObjectAnimator必须使用这个方法才能够访问到对象的属性,进而实现实时更新属性值的功能。例如,如果属性名为foo,你必须提供一个setFoo()方法。如果这个方法不存在,你有三种选择:添加这个方法,使用一个包装类(wrapper)或是改用ValueAnimator。
  • 如果你只为ObjectAnimator的工厂方法提供了一个属性值,那么ObjectAnimator会认为你提供的这个值是结束值,因此你还必须一个getPropertyName()方法,ObjectAnimator会以这个方法返回的值作为起始值。例如,如果属性名为foo,你必须提供一个getFoo()方法。
  • 根据属性的类别,你可能需要调用View的invalidate()方法来执行重绘,这样你的动画才能正常显示。可以在onAnimationUpdate()回调方法中这么做。例如,如果你通过动画改变的是一个Drawable对象的颜色属性,那么只有当这个对象重绘自己时,动画的效果才能显现出来。不过,View的所有setter,例如setAlpha()和setTranslation()会自动invalidate这个View,因此当你通过这些方法改变属性值时,你不需要手动调用invalidate()方法。

利用AnimatorSet编排动画

在很多情况下,你会想要根据其他动画的开始或结束的时间点来播放新的动画。Android系统提供了AnimatorSet类,你可以通过它将一系列动画组织在一起,确定是要同时播放、顺序播放或是延迟一段时间后播放。你甚至可以将一个AnimatorSet嵌套在另一个AnimatorSet内。
通过下面的代码,目标对象依次会作出以下行为:

  1. Plays bounceAnim.
  2. Plays squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2 at the same time.
  3. Plays bounceBackAnim.
  4. 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();

监听器

你可以通过下面的监听器来监听重要事件。
Animator.AnimatorListener提供了以下回调方法:

  • onAnimationStart():动画开始时调用;
  • onAnimationEnd():动画结束时调用;
  • onAnimationRepeat():动画重复播放时调用;
  • onAnimationCancel():动画被取消时调用。取消动画时也会调用onAnimationEnd(),无论它是怎么结束的。

ValueAnimator.AnimatorUpdateListener提供了以下回调方法:

  • onAnimationUpdate():在动画的每一帧调用。通过监听这个事件,你可以使用ValueAnimator生成的属性计算值。方法中会传递ValueAnimator对象,属性值通过getAnimatedValue()方法获取。如果你使用的是ValueAnimator,那么请实现这个方法。还有,正如前面所提到的,你可能会需要在这个方法中调用invalidate()方法来重绘对象。

如果你不需要实现所有的方法的话,你也可以继承AnimatorListenerAdapter类而不是实现Animator.AnimatorListener接口。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());
}

使用TypeEvaluator

如果你需要操纵的属性使用了对Android系统而言未知的属性,你可以通过实现TypeEvaluator接口创建自己的Evaluator。Android已知的属性有int,float和color,分别通过IntEvaluator,FloatEvaluator,ArgbEvaluator提供支持。
TypeEvaluator接口中只有一个方法需要实现——evaluate()方法。通过这个方法,Animator能够返回当前时间点下一个合适的属性值。下面是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);
    }
}

使用Interpolators

在动画系统中,Interpolator接受Animator提供的一个fraction(百分比),代表已经过去的时长。Interpolator根据Interpolator这个值以及Interpolators的类型计算出一个新的fraction,代表动画已经完成的部分。Android系统在android.view.animation中提供了一系列的Interpolator,如果还不能够满足需求的话,可以实现TimeInterpolator接口创建自定义的Interpolator。
这是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;
}

下表为两种不同的Interpolator得到的计算值(第一列为时间,第二列是LinearInterpolator的计算值,第三列是AccelerateDecelerateInterpolator的计算值):
Property Animation(属性动画)

指定KeyFrame(关键帧)

一个KeyFrame对象包含一对时间/值,让你能够定义动画的一个关键的时间点。每个KeyFrame对象还可以拥有自己的Interpolator,用于控制动画在前一个关键帧到这个关键帧的时间段中的行为。
KeyFrame对象必须通过ofInt(), ofFloat(),ofObject()这几个工厂方法来初始化。之后你就可以使用PropertyValuesHolder的工厂方法ofKeyframe()来初始化一个PropertyValuesHolder对象,并利用这个对象创建一个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);

为View制作动画

属性动画系统使得为View制作动画变得流水线化,并且拥有视图动画不具有的优势。视图动画系统在View对象的container内部通过改变View对象的绘制方式来达到动画的效果,这是因为View本身没有可以操纵的属性。这导致动画无法改变View对象本身。即使看起来View已经在其他位置了,但是实际上还存在于原来的地方。在Android 3.0版本,新的属性以及相应的getter和setter方法被加入,这个问题得到了解决。
属性动画系统可以通过改变它们实际的属性来改变它们在屏幕上显示的位置。此外,当属性值改变时,View会自动调用它们的invalidate()方法来刷新显示。View类提供的属性如下:

  • translationX and translationY:X与Y坐标的偏移量;
  • rotation, rotationX, rotationY:2D旋转(rotation)与3D旋转;
  • scaleX, scaleY:X与Y方向的缩放;
  • pivotX, pivotY:缩放与旋转的中心点,默认在对象的中心;
  • alpha:透明度,默认值为1(完全不透明),0代表完全透明(不可见)。
  • x,y:x == left + translationX,y == top + translationY。

你需要做的只有创建一个ObjectAnimator并选择一个属性,并设置各个参数(初始值、结束值等)。例如:

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

使用ViewPropertyAnimator制作动画

ViewPropertyAnimator提供了一种简单的方法来利用动画同时控制多个属性,利用一个隐藏的Animator对象。它的作用原理类似于ObjectAnimator,能够改变view的属性,但是它在同时操纵多个属性时效率高得多。此外,使用ViewPropertyAnimator编写的代码可读性更强,更简洁。下面展示使用三种不同方法同时操控view的x与y属性的代码:

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中声明动画

属性动画系统允许你在xml中声明动画,而不是编程解决。通过这种做法,你可以在不同的场合复用你的动画,而且可以更加方便地编排动画的次序。
为了和过去的视图动画框架区分开,从Android3.1开始,你应当将你的属性动画xml文件放置在res/animator文件夹内,并使用对应的标签:

  • ValueAnimator:animator
  • ObjectAnimator:objectAnimator
  • AnimatorSet:set

属性可见https://developer.android.google.cn/guide/topics/resources/animation-resource.html#Property
下面是一个示例,它将两个动画组合在一起,第一个动画内嵌套了两个同时开始的动画:

<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>

为了播放这个动画,你必须使用AnimatorInflater解析XML资源文件,并设置目标view,示例如下:

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

也可以像这样声明一个ValueAnimator:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

同样地,需要使用AnimatorInflater解析,并且需要实现AnimatorUpdateListener,如下:

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

参阅https://developer.android.google.cn/guide/topics/resources/animation-resource.html#Property