属性动画(一)

接下来的两篇文章会介绍 属性动画 相关的知识,如下图所示。本篇文章会介绍下图中绿色相关的知识。

PropertyAnimator.png

1. 简介

相比视图动画(View Animation),属性动画的功能还是非常强大的。

属性动画不仅可以对视图(View)的位置、大小、透明度、旋转进行动画操作,而且对于视图的背景颜色等属性也可以改变。

除此之外,属性动画还可以作用于视图之外的对象,比如自定义的类的对象。

一句话,只要是对象的属性都可以被属性动画所操纵。

因为还是属于动画的概念,所以需要设定一些动画相关的特性:

  • 持续时间:动画持续的时间,默认是 300 ms
  • 时间插值器(Time interpolation):用于定义属性的变化率,即用于计算在某一时刻,属性的值是多少
  • 重复次数和行为:定义是否重复和重复次数,行为表示是否翻转动画
  • 动画集合(Animator sets):可以将多个动画设置到一个集合中,并控制其中的执行顺序
  • 动画帧刷新频率(Frame refresh delay):动画多久刷新一帧,默认是每 10ms 刷新一帧

2. 工作原理

2.1 匀速平移动画

如下图所示,是一个简单动画的执行过程的示意图:

示意图1.png
  • 此示意图中的动画是匀速进行的
  • 40ms 的时间内, View 沿 x 轴向右平移 40px。每隔 10ms 都向右平移 10px,最后再 40ms 的时候,View 停在了 40px 的位置

2.2 非匀速平移动画

示意图2.png
  • 此示意图中的动画是非匀速进行的
  • 40ms 的时间内, View 沿 x 轴向右平移 40px。在初始阶段,View 是加速运动的,一直加速到中点;中点之后,开始减速运动,到达终点时,速度减为0。
  • 在初始阶段和结束阶段,同样的时间内,View 运动的距离比在中点附近同样时间内运动的距离短,因为中点附近时的速度是最大的。

2.3 属性值的计算原理

示意图3.png
  • 此示意图说明了在属性动画中,属性值是怎样计算出来的
  • ValueAnimator 用于追踪动画时间,比如:动画已经运行了多长时间,在当前时间点的属性值是多少
  • ValueAnimator 中包含两个对象:TimeInterpolatorTypeEvaluatorTimeInterpolator 是动画插值器,TypeEvaluator 用于定义在某一时间点,属性值该怎么计算
  • 动画开始时,创建一个 ValueAnimator 对象,并将属性值的初始值、结束值和动画执行时间赋予它。当调用 start() 方法时,动画开始。在整个动画执行期间,ValueAnimator 会基于动画的总时长和已运行的动画时间,计算动画的时间完成度(elapsed fraction),完成度值的范围是 01
  • ValueAnimator 计算完成时间完成度之后,它会调用当前设置的插值器 TimeInterpolator 计算插值完成度(interpolated fraction)。插值完成度是由时间完成度计算得到的。
  • 当计算得到插值完成度(interpolated fraction)之后,根据动画设置的初始值、结束值和插值完成度,通过 TypeEvaluator 即可计算得到当前的属性值。

3. 和视图动画的区别

视图动画(View animation)具有如下局限性:

  • 视图动画(View animation)只可以作用于视图(View)对象,对于非视图对象则难以处理,视图动画只可以改变视图的位置、大小、透明度和旋转,对于视图的其他属性也没办法改变。
  • 视图动画只是改变了视图显示的样式,并没有真正的改变视图的属性值

属性动画则视图动画的局限性,属性动画可以作用于视图对象和非视图对象,并且可以改变任何对象的任何属性。例如,可以作用于自定义的对象的属性值,也可以作用于视图对象的背景颜色、位置大小等属性。

4. 相关 API 一览

android.animation 包下可以找到属性动画相关的所有类,视图动画相关的类定义在 android.view.animation 包下。

Class Description
ValueAnimator ValueAnimator 是属性动画的核心类,其中包含动画相关的关键信息,包括动画时间相关的细节、动画是否重复、接收更新时间的监听器等等。属性动画包括两部分:1. 计算属性值;2. 将计算得到的属性值赋予对象的属性。ValueAnimator 并不会执行第二部,所以需要开发者监听属性值的变化,并根据你自己的逻辑更新对象的属性。
ObjectAnimator ObjectAnimatorValueAnimator 类的子类,它允许开发者设置目标对象和动画操纵的属性。在计算得到新的属性值时,这个类会相应地更新对象的属性。大多数时候,使用 ObjectAnimator 都是方便的,但有时候也需要使用 ValueAnimator,因为 ObjectAnimator 有一些限制,例如 ObjectAnimator 需要目标类有相应的访问目标属性的方法
AnimatorSet AnimatorSet 提供了一种将动画合并到一起的机制,比如将多个动画同时播放、按顺序播放等。

Evaluators 用于告诉属性动画系统怎么计算属性值。它根据 Animator 提供的时间值(开始时间和结束时间),计算当前的属性值。SDK 提供以下 evaluators

Class/Interface Description
IntEvaluator 用于计算 int 类型的属性值
FloatEvaluator 用于计算 Float 类型的属性值
AnimatorSet 用于计算 ARGB 颜色类型的属性值
TypeEvaluator 用于实现自定义属性值变化的 evaluator。如果你正在作用的对象的属性不是 intfloatargb 类型的,必须实现 TypeEvaluator 的子类去定义该属性值怎么变化。

插值器 Interpolators 是用于属性值的变化率。比如是属性值是加速变化的、减速变化的、还是先加速再减速。如果默认提供的 Interpolators 不能满足开发者的需求,则可以实现 TimeInterpolator 接口自定义插值器,规定属性值该怎样从初始值变化到最终值。

Class/Interface Description
AccelerateDecelerateInterpolator 先加速再减速(默认的插值器
AccelerateInterpolator 持续加速
AnticipateInterpolator 先向反向变化一下,再向前运动
OvershootInterpolator 动画到达终点时,会先超过一点,再回缩到终点值
AnticipateOvershootInterpolator 先反向,再向前运动,超过终点值一点,再回缩到终点值
BounceInterpolator 会在目标值处弹跳
CycleInterpolator 正弦 / 余弦曲线变化率
DecelerateInterpolator 持续减速直到0
LinearInterpolator 匀速
TimeInterpolator 实现自定义插值器时需要实现的接口

4.1 ViewPropertyAnimator

ViewPropertyAnimator 是实现视图(View)类对象属性动画非常方便的类

  • 当同时有多个视图对象的属性需要更新时,使用 ViewPropertyAnimator 的性能会比使用 ObjectAnimator 更好。因为 ViewPropertyAnimator 会一次性同时更新多个属性值并绘制,而 ObjectAnimator 则是将多个属性值分别更新并绘制
  • 使用 ViewPropertyAnimator 实现属性动画的语法也比使用 ObjectAnimator 实现属性动画的语法更简单

ViewPropertyAnimator 的使用方法如下所示:

  // 表示将此按钮 translationX 值渐变为 500
  Button button = (Button) findViewById(R.id.btn);
  button.animate()
        .translationX(500)
//      .translationXBy(500) 表示将 translationX 渐变地增加 500
        .setDuration(1000)
        .start();

ViewPropertyAnimator 在实现视图类对象的位置、大小、透明度、旋转的动画的时候,是非常方便的,使用方法和上面代码非常类似,具体可以参照 API 文档和源码注释。

4.2 ObjectAnimator

ObjectAnimator 的用法也非常简单,如下代码实现了 Button 按钮从 0px 向右平移到 500px 位置的动画

    Button button = (Button) findViewById(R.id.btn);
    ObjectAnimator animator = ObjectAnimator.ofFloat(button,"translationX",0f,500f);
    animator.setDuration(800);
    animator.start();

若要正确使用 ObjectAnimator,需要注意以下几点:

  • 动画作用的属性需要有一个名为 setXXX() 的方法,因为属性动画更新对象的属性值,所以该对象必须要有一个可以修改该属性的 setXXX() 方法。比如,若动画作用的属性名为 foo,则该对象必须要拥有一个名为 setFoo() 的方法供属性动画使用。若没有这样的方法,有以下几个解决办法:
    • 如果该类可以修改,增加对应的 setXXX() 方法
    • 如果可以修改该属性,可以使用一个包装类,增加对应的 setXXX() 方法,将接收到的属性更改值,传递到原始对象的属性
    • 使用 ValueAnimator,而不是 ObjectAnimator
  • 如果在 ObjectAnimator 的工厂方法中只传递进去一个属性值,则该属性值是该动画的结束值。所以此对象的这个属性值也必须拥有一个 getXXX() 方法,用于获得动画的初始值。例如,若动画作用的属性名为 foo,则该类需要有一个 getFoo() 的方法
  • 属性的 getXXX() 方法和 setXXX() 方法中参数的类型,必须和 ObjectAnimator 静态工厂中的方法的类型相同。例如,若有一个对象是 targetObject.setPropName(float)targetObject.getPropName(float),则创建对应的 ObjectAnimator 对象时,需要时如下的代码:
  ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 对于有些对象的属性,如果目标对象是 View 对象时,属性动画更新属性值的时候,需要调用 invalidate() 方法强制地重新绘制这个 View 对象。此步骤发生在 onAnimationUpdate() 回调方法中。例如,若属性动画作用于一个 Drawable 对象的颜色时,只有重新绘制该 Drawable 对象,则颜色属性才会生效。对于 View 对象的 setAlpha()setTranslationX()等属性值,在属性动画作用的时候,都会正确地重绘,所以不必关心它们。

4.3 ValueAnimator

ValueAnimator 可以在某一段时间内,对一些类型的值进行改变。通过调用它的 ofInt()ofFloat()ofObject() 工厂方法可以得到 ValueAnimator 的对象。例如:

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

上述代码,当调用 start() 方法动画开始时,动画的值在 1000ms 内,从 0 变化到了 100
可以为 ValueAnimator 添加监听器,监测在这段时间内动画值的变化情况,如下代码所示:

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

4.4 AnimatorSet

在许多情况下,一个动画需要在另一个动画开始或结束时开始执行。系统提供的 AnimatorSet 类可以将多个动画组合在一起,动画之间和同时播放,可以按顺序播放或延时播放。

同样,也可以将多个 AnimatorSet 嵌套使用。

下面是一个使用 AnimatorSet 播放组合动画的例子:

  • 首先播放 bounceAnim
  • 然后同时播放 squashAnim1, squashAnim2, stretchAnim1stretchAnim2
  • 接着播放 bounceBackAnim
  • 最后播放 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();

4.5 PropertyValuesHolder

PropertyValuesHolder 可以实现在一个动画中,多个属性同时变化的情况,比如:一个 Button 按钮,在从小不断变大的过程中,透明度也在同时发生着变化,代码如下所示:

  Button button = (Button) findViewById(R.id.btn);
  PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);  
  PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);  
  PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);

  ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(button, holder1, holder2, holder3)  
  animator.setDuration(800);
  animator.start();  

提到 PropertyValuesHolder,还有一个类也需要提一下 --- Keyframe(关键帧)。通过 Keyframe 可以将一个动画拆分成多个阶段。如下代码所示:

  Button button = (Button) findViewById(R.id.btn);
  // 在 0% 处开始
  Keyframe keyframe1 = Keyframe.ofFloat(0, 0);  
  // 时间经过 50% 的时候,动画完成度 100%
  Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);  
  // 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
  Keyframe keyframe3 = Keyframe.ofFloat(1, 80);  
  PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("scaleX", keyframe1, keyframe2, keyframe3);

  ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(button, holder);  
  animator.start();

4.6 动画监听器

在动画运行期间,可以通过以下监听器监听动画的关键时刻:

public static interface AnimatorListener {
    // 在动画开始时被调用
    void onAnimationStart(Animator animation);

    // 在动画结束时被调用
    void onAnimationEnd(Animator animation);

    // 在动画取消时被调用
    void onAnimationCancel(Animator animation);

    // 在动画重复执行时被调用
    void onAnimationRepeat(Animator animation);
}
public static interface AnimatorPauseListener {

       //在动画暂停时被调用
       void onAnimationPause(Animator animation);

       // 在动画被重新执行时被调用
       void onAnimationResume(Animator animation);
   }
public static interface AnimatorUpdateListener {
    // 在动画刷新每一帧的时候都会被调用
    void onAnimationUpdate(ValueAnimator animation);
}
  • AnimatorUpdateListener.onAnimationUpdate(ValueAnimator animation) 方法中有 animation 参数,通过参数 animation.getAnimatedValue() 可以得到当前动画执行的完成度的值。

  • 如果不想实现 AnimatorListener 接口的所有方法,也可以继承 AnimatorListenerAdapter,选择想实现的方法。例如,以下代码通过实现 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());
  }

参考资料:

官方文档

Android基础——动画(二) -- 林于卫国

HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇) -- HenCoder

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,688评论 0 10
  • 一 为什么引入属性动画 补间动画只有四种(移动、缩放、旋转和淡入淡出) 补间动画还有一个缺陷,就是它只能够实现移动...
    scarerow阅读 453评论 0 0
  • Android 动画之属性动画(一)# 说到动画,先给大家一点概念。众所周知,动画其实是很多帧的图片以小于人类眼睛...
    梁加盐阅读 595评论 0 0
  • 每次看到身边有趣的人,我就对自己说,再等等吧。
    _李想ang阅读 130评论 0 0
  • 如果有一天我一无所有的时候 我就去打零工 做兼职 我把挣来的钱都花在我的爱好和旅游上面 就像这牛粪里的草一样还挣...
    南卡muge阅读 133评论 0 0