Android 属性动画

一. 简介

属性动画(Property Animation)是Android 3.0(API 11)之后提供的动画框架,在之前Android 提供了 帧动画(Frame Animation) 和 补间动画(Tween Animation),帧动画就是把一个动画分成多张图片,然后把这些图片连贯起来播放,原理和动画片类似;补间动画是针对View 进行的平移、旋转、缩放、透明度变化 等动画效果;属性动画则是通过改变属性值来实现动画效果,不再局限于View。

属性动画和补间动画的对比:

  • 作用对象
    补间动画的作用对象仅为View,而属性动画则可作用于非View 对象。有些情况下,可能需要对View 的某个对象进行操作,而不是整个View 对象,比如动态改变View 的颜色,此时补间动画便无法实现。

  • 对View 的影响
    补间动画只改变View 的视觉效果,并不改变View 的属性,比如将一个Button 从屏幕左上角 移动至 右下角,使用补间动画的话,移动完成之后 Button 的点击区域还是左上角,因为它的位置属性并没有改变。而属性动画,顾名思义,改变的就是属性。

  • 动画效果
    补间动画效果比较单一,仅支持平移、旋转、缩放、透明度变化,而属性动画比较灵活,可以实现多种复杂的动画效果。

在现在的开发过程中,由于属性动画已经可以实现几乎所有补间动画能实现的功能,所以我们最常用的还是属性动画,本篇将简介属性动画的基本使用。

二. 工作原理及相关类

1. 属性动画的工作过程
工作过程.png

从上面的工作过程可以看到,属性动画框架中最常用也是最重要的几个类,就是ValueAnimator、ObjectAnimator、Interpolator 的实现类 和 TypeEvaluator 的实现类。

2. 相关类简介

Animators:
1)ValueAnimator,属性动画的主要计时引擎,它还计算要设置动画的属性的值。它具有计算动画值的所有核心功能,包含每个动画的持续时间,有关动画是否重复进行,更新属性值的事件侦听器以及设置自定义类型的估值器功能。属性动画有两个部分:计算动画过程中的属性值,给对象和属性上设置这些值。ValueAnimator 仅负责计算值,因此必须监听ValueAnimator 计算的值的更新,并使用按照自己的逻辑修改要设置动画的对象。

主要方法:

方法签名 用途
public static ValueAnimator ofInt (int... values) ofInt() 作用有两个:
1. 创建动画实例
2. 将传入的多个Int参数进行平滑过渡: 如果传入0和1,表示将值从0平滑过渡到1,如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推。
ValueAnimator.ofInt() 内置了整型估值器,直接采用默认的,不需要设置,即默认设置了如何从初始值 过渡到 结束值。
public static ValueAnimator ofFloat (float... values) 类似ofInt
public static ValueAnimator ofArgb (int... values) api 21 时加入,传入的参数是颜色的int 值,提供颜色值的平滑变换,和ofInt 的区别在于使用的估值器不同
public static ValueAnimator ofObject (TypeEvaluator evaluator, Object... values) 可传入对象作为参数创建Animator 实例,由于可以传任意对象,所以需要自己提供估值器
public ValueAnimator setDuration (long duration) 设置动画持续时长,单位毫秒
public void setRepeatCount (int value) 设置动画重复次数
public void setRepeatMode (int value) 设置动画重复模式,有两种取值:RESTART 或 REVERSE,顾名思义就是重新开始 和 倒着执行
public void setStartDelay (long startDelay) 设置延迟多长时间开始动画,单位毫秒
public boolean isRunning () 判断动画是否正在运行
public void addUpdateListener (ValueAnimator.AnimatorUpdateListener listener) 添加值变化时的监听事件,若要使用ValueAnimator 实现动画就必须要添加一个Listener,在数据变化之后改变对象属性来实现动画
public void start () 开始动画,如果没有设置延时开始,动画会立即开始,设置了则会延时一段时间后开始
public void cancel () / public void end () 取消动画 / 结束动画,两者的区别就是调用cancel 会停止在当前状态,end 会将属性值设置为结束值
public void addListener (Animator.AnimatorListener listener) 是父类Animator 的方法,添加Listener,监听动画开始、结束、取消、重复等事件
public void pause () / public void resume () 暂停 / 重新开始动画,调用这两个方法的线程必须和动画开始的线程相同,另外,如果对一个没有暂停的动画调用resume 方法将会被忽略

以上,只是比较常用的方法,还有许多诸如getter 等方法,这里不一一列举,详情可参官方文档

2)ObjectAnimator,是ValueAnimator的子类,允许我们将目标对象和对象属性设置为动画,它在计算动画的新值时会相应地更新属性,我们在大多数情况下可以使用ObjectAnimator,因为它使得在目标对象上设置动画值的过程变得更加容易。但是,ObjectAnimator还有一些限制,例如要求在目标对象上存在特定的acessor方法。

主要方法和ValueAnimator 类似,只是创建对象的ofXXX 方法有些不同,常用如下

方法签名 用途
public static ObjectAnimator ofArgb (Object target, String propertyName, int... values) 构造并返回一个在颜色值之间设置动画的ObjectAnimator,values 传单个值意味着该值是动画的结束值,在这种情况下,起始值将从正在执行动画的属性的值和第一次调用start() 时的目标对象产生。两个值的话意味着起始值和结束值。两个以上的值意味着起始值,沿途的动画值和结束值
public static ObjectAnimator ofFloat (Object target, String xPropertyName, String yPropertyName, Path path) 使用两个属性沿Path设置动画,“路径”动画以二维方式移动,将坐标(x,y)设置为动画,以跟随线条。坐标是浮点数,它们被分别设置到由xPropertyName和yPropertyName指定的属性。
public static ObjectAnimator ofFloat (Object target, String propertyName, float... values) 为指定对象target 的指定属性propertyName 设置值
public static ObjectAnimator ofInt (T target, Property<T, Integer> xProperty, Property<T, Integer> yProperty, Path path) 和上面提到的ofFloat 类似,只不过参数值的类型是int 值

需要注意的是,在给对象的指定属性设置值时,要求对象内必须有属性对应的getter 和setter。更多方法可以查文档

3)AnimatorSet

提供一种将动画分组在一起的机制,以便它们相互运行,可以将动画设置为一起播放,按顺序播放,或在指定的延迟后播放。主要方法如下

方法签名 用途
public AnimatorSet.Builder play (Animator anim) 此方法创建一个Builder对象,用于设置动画之间的播放约束。这个初始的play() 方法告诉Builder动画,它是对Builder的后续命令的依赖。例如,调用play(a1).with(a2) 将AnimatorSet 设置为同时播放a1和a2,play(a1).before(a2) 设置AnimatorSet首先播放a1,然后播放a2,而 play(a1).after(a2) 将AnimatorSet 设置为先播放a2,然后播放a1。请注意,play() 是告诉Builder 创建依赖关系的动画的唯一方法,因此对Builder 中各种函数的连续调用都将引用play() 中提供的初始参数作为其他动画的依赖关系。例如,当a1结束时,调用play(a1).before(a2).before(a3) 将同时播放a2和a3; 它没有在a2 和a3 之间建立依赖关系。
public void playSequentially (List<Animator> items) 顺序执行动画列表
public void playTogether (Collection<Animator> items) 同时播放集合中的动画
public void setCurrentPlayTime (long playTime) 设置时间进度到指定的时间点,值应该在0 和 动画集结束的总时长 之间
public void reverse () 反转执行动画,如果使用setCurrentPlayTime 跳到了某时间点,将在该时间点反转,否则将从结束值开始反转,这个方法值适用当前动画,未来的动画将不受影响
public AnimatorSet setDuration (long duration) 设置动画集中每一个动画的执行时长,默认情况每个动画执行自己的时长,调用了这个方法后,每个动画的执行时长将使用设置值

更多方法可见这里

Interpolator:

时间插值器定义如何计算动画中的特定值作为时间的函数。例如,可以指定动画在整个动画中线性发生,这意味着动画在整个时间内均匀移动,或者还可以指定动画以使用非线性时间,例如,在开始时加速并在结束时减速。Android 提供了一些插值器,如果所提供的插值器都不适合需求,可以实现TimeInterpolator接口并创建自定义插值器。

Android 默认提供了9 种插值器

描述
LinearInterpolator 值随时间的变化为线性
image.png
AccelerateInterpolator 加速变化
image.png
DecelerateInterpolator 减速变化
image.png
AccelerateDecelerateInterpolator 先加速后减速变化
image.png
AnticipateInterpolator 先反向变化,再正向快速变化
image.png
OvershootInterpolator 快速变化到超出结束值一段,再缓慢反向变化回结束值
image.png
BounceInterpolator 不断回弹地变化
image.png
CycleInterpolator 正弦函数变化
image.png

Evaluator:

Evaluator(估值器)告诉属性动画系统如何计算给定属性的值,它们获取Animator类提供的时序数据(时序数据的值就是通过Interpolator 计算出的),动画的开始和结束值,并根据此数据计算属性的动画值。

Android 提供了三种估值器:

描述
IntEvaluator Int 值属性的默认估值器
FloatEvaluator Float 值属性的默认估值器
ArgbEvaluator 属性值表示颜色时,使用该估值器

如果我们需要做动画的属性的类型不是int 或 float时,就需要实现TypeEvaluator 接口自定义估值器。

三. 应用

1. ValueAnimator

在使用ValueAnimator 实现动画时,首先要创建一个Animator 对象,上面的API 简介提到了一系列的of 方法,可以创建针对不同属性类型的Animator 对象

    val intAnim = ValueAnimator.ofInt(0, 1000).apply {
            duration = 5000
            startDelay = 1000
            interpolator = BounceInterpolator()
            addUpdateListener {         // 在监听值更新的Listener 里给要做动画的对象(本例是一个TextView)的属性赋值
                animText.x = (it.animatedValue as Int).toFloat()
                animText.rotationY = (it.animatedValue as Int).toFloat()
            }
            addListener(object : Animator.AnimatorListener {            // 监听动画的执行事件
                override fun onAnimationStart(animation: Animator?) {
                    println("-------------start")
                }

                override fun onAnimationEnd(animation: Animator?) {
                    println("-------------end")
                }

                override fun onAnimationCancel(animation: Animator?) {
                    println("-------------cancel")
                }

                override fun onAnimationRepeat(animation: Animator?) {
                    println("-------------repeat")
                }
            })
        }

除了用Java/Kotlin 代码创建外,还可以在XML 中创建动画的相关设置:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="1000"
    android:valueType="intType"
    android:duration="5000"
    android:startOffset="1000"
    android:repeatCount="1"
    android:repeatMode="reverse">

</animator>

加载XML 并创建对象的代码如下:

val xmlAnim = AnimatorInflater.loadAnimator(this, R.animator.value_anim)

注意在使用XML 创建Animator 对象之后,也需要添加Listener 来动态改变对象的属性值。

创建了Animator 对象之后,若要开始动画,只需要调用start 方法即可。

2. ObjectAnimator

ObjectAnimator 是ValueAnimator 的子类,对其进行了一定程度的封装,我们不需要再添加监听值变化的Listener 来手动为属性赋值,我们只需在创建ObjectAnimator 对象时,传入要改变属性的对象及其属性名即可(但要保证该属性有对应的setter 方法),如下:

val colorAnim = ObjectAnimator.ofArgb(textView, "textColor", Color.parseColor("#dc5f26"), Color.parseColor("#2684DC")).apply {
    duration = 5000
}

同样,ObjectAnimator 的相关设置也可以通过XML 来写,

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="800"
    android:valueType="floatType"
    android:duration="5000"
    android:startOffset="1000"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:repeatCount="1"
    android:repeatMode="restart"
    android:propertyXName="x"
    android:propertyYName="y"
    android:pathData="M10,10Q200,100,400,600Q600,100,800,0Z">

</objectAnimator>

同样需要加载对象

val xmlAnim = AnimatorInflater.loadAnimator(this, R.animator.obj_anim)
xmlAnim.setTarget(textView)                 // 设置动画作用的对象

开始动画只需调用start 方法即可。

我们知道,属性动画的动画效果就是通过不断改变对象的属性值来实现的,所以要比补间动画更灵活,那么补间动画能实现的View 的四种动画效果,放在属性动画里应该改变哪些值呢,如下表

属性 作用 数值类型
alpha 控制View的透明度 float
translationX 控制X方向的位移 float
translationY 控制Y方向的位移 float
translationZ 控制Z方向的位移 float
scaleX 控制X方向的缩放倍数 float
scaleY 控制Y方向的缩放倍数 float
rotation 控制以屏幕方向(可以理解为Z 轴)为轴的旋转度数 float
rotationX 控制以X轴为轴的旋转度数 float
rotationY 控制以Y轴为轴的旋转度数 float

这些都是View 的属性,通过对他们的改变就可以实现一些基本的动画效果。

3. AnimatorSet

AnimatorSet 可以将多个动画组合起来进行播放,并且可以给这些动画设置先后顺序等

        val path = Path().apply {
            moveTo(10f, 10f)
            quadTo(200f, 100f, 400f, 600f)
            quadTo(600f, 100f,800f, 0f)
        }
        val pathAnim = ObjectAnimator.ofFloat(textView, View.X, View.Y, path).apply {       // 创建一个让View 按照指定Path 运动的动画
            duration = 3000
            interpolator = AccelerateDecelerateInterpolator()
        }

                // 颜色改变的动画
        val colorAnim = ObjectAnimator.ofArgb(textView, "textColor", Color.parseColor("#dc5f26"), Color.parseColor("#2684DC")).apply {
            duration = 5000
        }

                // 改变横坐标的动画
        val intAnim = ObjectAnimator.ofFloat(textView, "x", 0f).apply {
            duration = 3000
        }

        val animSet = AnimatorSet()
        animSet.play(pathAnim)          // 调用play 来播放
        animSet.play(colorAnim).with(intAnim).after(pathAnim).after(2000)           // with 表示同时播放,after 表示在xxx 之后播放,before 表示在xxx 之前播放

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

推荐阅读更多精彩内容