【Android学习笔记】属性动画基础学习笔记

属性动画

属性动画系统是一个具有鲁棒性的框架,允许你几乎让一切都动起来。你能够定义一个动画来随着时间改变任何对象的任何属性,无视该对象是否是画在在屏幕上的。属性动画在指定的时间内改变属性值(某个对象的某个属性)。为了让目标动起来,需要特别指明所要运动的目标的属性,例如目标在屏幕上的位置,运动的时间长短和运动的值的范围。

属性动画系统允许定义以下属性来用于动画。

  • 动画时间 duration,可以明确规定动画执行的时间,默认长度是300毫秒。
  • 时间插值 time interpolation,作为动画当前已经执行时间的函数来定义属性值如何计算...You can specify how the values for the property are calculated as a function of the animation's current elapsed time.动画的变化速率。
  • 重复次数和行为,设置当目标到达结束时间时是否重复执行先前的动画和重复的次数。另外也可以定义动画是否反向执行,这样动画就会反复的正向执行、反向执行,直到次数达到指定的值。
  • 动画行为集合,可以将一系列动画放置到一个集合中,指定全部同时执行,或者顺序执行,又或者有时间延迟的执行。
  • 帧刷新时间,定义刷新动画的时间,默认的刷新时间是10毫秒,但是设备的帧数最后基本上是由系统的繁忙程度,和系统的硬件水平。

属性动画的工作方式

详细看一下属性动画系统的重要组件,下图说明了主要类的工作方式。

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

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

  • 视图动画系统仅仅能够使得view及其子类动起来,所有如果想赋予非view对象动画效果,只能自己来实现相关代码。而且视图动画也仅仅局限于使用view的一部分属性,例如缩放、旋转角度,但是包括例如背景色等属性。
  • 视图动画另一个缺陷在于仅仅能修改视图中被画下来的部分,并且这些改变不真正作用于自身实际。例如为一个按钮添加穿过屏幕的效果,但是按钮在动的过程中,它实际的位置,可以相应点击事件的位置没有改变,所有你不得不实现自己的逻辑来控制它。
  • 对于属性动画来说,则没有上面的约束,任何对象的所有属性都可以赋予动画效果并且这种改变是真实有效的。而且在执行过程中更加具有鲁棒性。在更高的层面上,你赋予动画器到你希望动画化的属性,诸如颜色、位置、或大小,而且可以定义动画的切面诸如多动画器的插值和同步。
  • 而视图动画只需要更少的启动时间、更少的必须代码。如果视图动画以及能满足需求或者现有的代码正在和期望一样工作,那么没有必要使用属性动画。当然,在一些不同情况下,二者相结合使用也是合理的方法。

API预览

绝大多数的api都可以在android.animation找到,在android.view.animation已经定义了许多可以使用的插值。下面的内容是对于属性动画主要的组件做介绍。

动画器 Animators

ValueAnimator

属性动画系统中主要的用来计算属性值变化的计时器,拥有所有的计算功能的核心方法,来计算动画值,为每个动画效果记录时间和是否重复播放、接收更新事件的监听器等相关信息,也可以保持用来计算的自定义类型的集合。使得目标动起来主要由两部分的任务,一是计算属性的实时变化值,二是将这些结果值赋予要进行运动的目标的属性。ValueAnimator并不执行第二部分的任务,所以必须来对数值的更新作出监听,并用自己的逻辑来将更新值赋予目标属性。

ObjectAnimator

ValueAnimator的子类,允许设置目标对象和对象的属性。这个类在计算出动画的更新值后相应的会将这个值更新到属性上。由于更新目标对象的属性值这个过程更加容易,所以ObjectAnimator的使用会更加频繁。然而由于ObjectAnimator要求目标对象需要拥有获取属性的特定set、get方法,所以有时ValueAnimator也是有发挥之地的。

AnimatorSet

提供了一个组合动画的机制,使得多个动画过程之间可以相关管理。可以使得多个动画同时发生、顺序发生或者延迟特定时间发生。

动画器告诉属性动画系统对于给定的属性如何计算其变化值。由Animator这个类来提供定时数据,基于这个数据来计算动画起始和结束的属性值。属性动画系统提供了以下的属性计算器(evaluators)。

计算器 evaluators

IntEvaluator

用来计算int值类型属性的默认计算器。

FloatEvaluator

同上,计算float类型

ArgbEvaluator

同上,计算颜色值,16进制

TypeEvaluator

允许创建自己的计算器的接口。如果需要计算的属性类型不是整型或者浮点型或者颜色值,就必须要实现该接口来指定如何计算对象的属性值。如果计算整型、浮点型和颜色值的计算方式和默认的计算器不同,同样也可以使用这个接口。

时间插值器(time interpolator)定义了动画过程中计算属性值的时间函数。例如,在整个动画过程中指定一个线性的动画,意味着动画在执行时间内是均匀发生的,又或者定义一个非线性动画,在开始加速在结束时减速。下面介绍了在android.view.animation中的插值器。如果没有一个能满足需求,可以实现TimeInterpolator这个接口。

插值器 Interpolators

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方法,方法格式为set(),由于ObjectAnimator类会自动的在执行动画期间更新属性值,所以必须能够有获得相应属性的set方法。例如,属性名称是foo,对应的set方法名称应该是setFoo()。如果没有对应的set方法,解决途径有三个:1.如果有权限,在目标类中添加对应的set方法;2.使用一个有权限去改变并且能够通过set方法接收合法set参数的包装类,并且把这个包装类指向原本的对象;3.使用ValueAnimator
  • 如果在某个ObjectAnimator类的工厂方法中的values...参数位置上只添加了一个参数,那么这个值会被当做动画结束时的属性值,因此,目标属性应当拥有get方法,这个get方法用来获取动画起始时候的属性值,格式为get(),命名方法同set方法。
  • 目标属性的set和get方法必须和在ObjectAnimator类中指定的起始值结束值的类型一致。例如,如果像这样来初始化一个动画对象ObjectAnimator.ofFloat(targetObject, "propName", 1f),必须拥有targetObject.setPropName(float)targetObject.getPropName(float)方法。
  • 由于想要改变属性值来达到动画的效果,所有需要调用invalidate()方法来迫使视图使用新的属性值来重画自身。这一步骤将在onAnimationUpdate()方法中被调用。例如,对一个Drawable对象的颜色属性设置动画效果,只有在这个对象重画自身的时候才能在屏幕上体现出来。视图中所有类似于d setTranslationX()setAlpha()的set方法都会适时的重绘视图。所以在调用这些方法时,不需要使用者自己调用。

ofXXX方法参数说明

ofFloat(Object target, String propertyName, float... values) 第1个参数为目标对象;第2个参数为属性名,这里要求目标对象要有“set属性名()”的方法。后面是可变参数,表明属性目标值,一个参数表明是终止值(对象要有get属性方法),可变参数的个数为2时,表明第一个是起始值,第二个是终止值;更多个参数时,动画属性值会逐个经过这些值

使用AnimatorSet来组合动画,设计复杂效果

在很多情况下,一个完整的动画可能需要一个动画效果在另一个动画效果执行之前或者之后再执行。Android系统允许将多个动画效果绑定,置于AnimatorSet中,对于这些动画设置特定次序,同时、顺寻或者以一定延时开始。同样,AnimatorSet也可以相互嵌套。下面的示例代码来自于Bouncing Ball,以一定顺序的动画效果组合完整动画。

演示顺序:

  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();
    

Animation 监听器

可以实现动画监听器来在动画执行期间监听重要的事件并作出反应。 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的布局动画

属性动画系统对于ViewGroup提供了同样操作简单的途径来实现动画效果。

使用LayoutTransition([trænˈzɪʃn])这个类来为布局变化添加效果。在ViewGroup内的子view可以再添加、移除、调用setVisibility()方法设置是否可见时伴随出现和消失的动画效果。至于在视图容器中已经存在的试图,则可以在有新视图添加到容器中或者移除某个视图,伴随动画移动到新的位置。通过调用setAnimator()方法,将下列效果添加入一个LayoutTransition对象,并且传入一个Animator对象和一个LayoutTransition常量,来达到上述效果。

  • APPEARING,当容器内添加视图时,该动后作用于被添加视图。
  • CHANGE_APPEARING,由于其他视图被添加容器,导致其余视图位置改变时,该动作作用于位置改变的视图。
  • DISAPPEARING,作用于容器内被移除或者消失的试图。
  • CHANGE_DISAPPEARING,由于其他视图被移除出容器,导致其余视图位置改变时,该动作作用于位置改变的视图。

亦可以以自定义动画来取代上述四种默认效果的动画。

在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

如果在设计动画时,涉及到值类型是自定义类型的或者是基本类型的组合,可以通过实现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()方法来重绘自身,这些属性值是:

  • translationXtranslationY,相对于初始位置的偏移量,左负右正,上负下正。
  • rotation,rotationX,rotationY,控制旋转的参数。
  • scaleX,scaleY,控制横纵方向缩放的参数。
  • pivotX,pivotY,表示缩放的中轴点XY坐标,pivot point默认为对象中心位置。
  • x,y,距离父容器左上角的坐标。
  • alph,透明度,1表示不透明,0表示透明。

上面的这些属性都可以使用ObjectAnimator来使用。

使用ViewPropertyAnimator制作动画

ViewPropertyAnimator类提供了一种简便的方法来让一个view的多种属性在一个动画过程中并行改变。同时改变多个属性的值,但是更加高效,它的行为很像ObjectAnimator。此外使用ViewPropertyAnimator的代码也更加精炼和易读。下面的代码片段显示了使用ObjectAnimatorViewPropertyAnimator的异同。

使用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();

备注

  1. 什么是插值。

插值问题的提法是:假定区间[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*)的近似。

  1. view.getX和view.getTranslationX区别。

.view.getTranslationX计算的是该view的偏移量。初始值为0,向左偏移值为负,向右偏移值为正。view.getX相当于该view距离父容器左边缘的距离,等于getLeft+getTranslationX。

参考文章

Property Animation

android动画(一)Interpolator

缓冲动画函数

你可能感兴趣的:(android学习笔记)