转载请注明出处:http://blog.youkuaiyun.com/My_TrueLove/article/details/52262935
访问 ruicb.com,一键抵达我的博客!
注:本文偏度较长,越往后越精彩,请务必从头开始阅读,循序渐进。
有关 Android 动画的发展已经老生常谈了,我总结一下从别处学来的,作为开头。我们知道在 Android 3.0 之前只有逐帧动画(Frame Animation)和补间动画(View Animation)。逐帧动类似于我们常见的GIF,将多张图片按照一定的顺序、时间间隔轮流播放,形成简单的动画效果,在有些场景下还是比较实用的;补间动画是Android提供的针对View(及其子类)的动画操作类,包含平移(translation)、旋转(rotation)、缩放(scale)、透明度变换(alpha)这四个操作。
在3.0之后呢,Google为Android添加了属性动画(Property Animation
),致力于为开发者提供更好的动画解决方案。有别于补间动画的是,属性动画已不再针对View设计,而是更偏向于对值进行动画变换,可以操作任何对象的任意属性
,这一点先记住,后面会讲到,因为本文的主角就是属性动画!
1. 属性动画 VS 补间动画
既然有了补间动画,为什么Google还要设计属性动画呢,原因很明显。最明显的就是属性动画解决了补间动画移动后点击时间留在原位的问题;其次就是属性动画扩展性高,不再局限于补间动画的那四种变换。总之,相比于补间动画,我更推荐易用、实用性高的属性动画,接下来,就一起来全面认识一下属性动画吧。
2. API 一览
我打算在介绍之前,先抛出我们接下来要介绍的内容,这样你带着这些东西去读,会更有目的性,也更加容易融会贯通。注意,以下有关作用的描述翻译自源码中的注释文档,因为我觉得只有官方的才是最权威的,天朝一大抄的氛围,已经让我不敢轻易相信有些东西了。翻译不到位的,还请谅解。同时还有许多没有提及的,所以建议读者自己去查看,体会一下。
2.1 abstract class Animator
继承关系:无父类(除Object)
作用:是所有提供基础动画支持的类的超类,这些动画应当可以被开始、结束,并可以添加动画监听。
补充:知道就行了
2.2 class ValueAnimator
继承关系:ValueAnimator extends Animator
作用:本类为运行动画提供了一个简单的计时引擎(timing engine),用来计算动画完成值(animated values)并设置到目标对象上。
补充:可以看出,其仅针对Value(值)进行动画变换,但它却是属性动画的核心类,后面要介绍的 ObjectAnimator
就是在 ValueAnimator
基础上进行的二次封装。
2.3 final class ObjectAnimator
继承关系:ObjectAnimator extends ValueAnimator
作用:本类为 ValueAnimator 的子类,支持对目标对象的属性进行动画(animating properties on target objects)。这个类的构造函数,通过参数定义需要进行动画操作的目标对象,以及动画所针对的属性名称。确定对象内部存在合适的 getter
和 setter
方法,然后动画将在必要时调用这些方法以对属性进行动画。
补充:该类使用 final
修饰,无法再被其他类继承;其直接对目标对象的具体属性进行动画操作,前提是该属性存在相应的 setter
和 getter
方法。
2.4 AnimatorSet extends Animator
继承关系:AnimatorSet extends Animator
作用:本类可以在特定的顺序下播放一组 Animator 对象动画(plays a set of Animator objects in the specified order)。动画可以被设置为同时播放、异步播放或者在特定的延时之后播放。
补充:AnimatorSet
操作的对象为 Animator
,ValueAnimator
和 ObjectAnimator
都是 Animator
的子类,所以都可以借助 AnimatorSet
实现组合动画。
有了以上几个类(实际常用的是 ObjectAnimator 和 AnimatorSet),我们就可以使用属性动画基本的功能实现大多数动画效果啦,想想都很激动,下面就开始正式介绍基础篇的内容。
3. 基础部分
下面将重点介绍几个类的用法,由于在 API 一览中已经对各个类进行了简单的介绍,所以接下来就直奔主题。
3.1 ValueAnimator
其为属性动画的核心类,但实际使用中,我们一般很少与其直接打交道,但不代表我们不需要了解他的用法。一般情况下,我们都会使用其提供的静态方法 ofXXX()
实现相应的值动画,包含以下几个方法:
下面以 ValueAnimator.ofFloat(float... values)
为例重点讲解一下用法:
1. 基本用法
实现在100ms内,将一个值从0过渡到1,并打印出来:
打印如下,默认刷新频率为10ms,但实际频率视实际情况有所波动:
OK,成功的将值打印出来了,是不是很简单。但是细心地你会发现,值得变化并不是均匀的,明显感觉值是先加速增长,后减速增长。确实是这样,对属性动画有一定了解的或许知道插值器(TimeInterpolator)这个东西,不知道的也没关系,后面会介绍。在这儿你只需知道,属性动画默认使用了先加速后减速的插值器(The default value is AccelerateDecelerateInterpolator)
2. 更多方法
除了为 ValueAnimator
设置动画时常、添加更新监听外,我们还可以为 ValueAnimator
设置动画重复的次数、重复模式、以及添加其它监听。详见下图,图中演示的两种监听,根据自己需要选其一即可:
3. 参数介绍
ofFloat(float... values)
方法接收可变参数,意味着我们可以传一个甚至多个参数。但是,根据下图可知,官方建议我们传两个以上参数。
但我们依然可以传一个参数,并且程序依然正常运行。因为此时系统会默认将我们传入的唯一参数作为结束值,而开始值则默认为 0,下面我们通过源码验证一下。跟踪源码,会进入KeyframeSet
类的 ofFloat(float... values)
方法,其中包含下面一段代码(有删减,并添加注释):
是不是很有说服力!?源码之下,了无秘密,要我说最好的文档还是官方源码和注释文档!
现在,传入一个参数、两个参数我们都知道了,那么n个值(n>2)呢?其效果就等价于 n-1
个平均耗时 duraction/(n-1)
的动画连续进行平滑过渡。具体的就不再细说,感兴趣的可以自己打印值看看结果。
除了 ofFloat(float... values)
方法,还有类似的 ofInt(int... values)
和 ofArgb(int... values)
,使用方式基本一样,就不再一一介绍。除此之外,剩下的ofObject(TypeEvaluator evaluator, Object... values)
和 ofPropertyValuesHolder(PropertyValuesHolder... values)
方法,将留在进阶篇介绍,暂时不影响我们学习后续内容。
3.2 ObjectAnimator
ValueAnimator
介绍的篇幅较多,实际上是在为 ObjectAnimator
减轻压力,因为前面说过 ObjectAnimator
是 ValueAnimator
子类,ValueAnimator
的方法在 ObjectAnimator
中同样适用,且动画特征都一样。下面着重介绍 ObjectAnimator
所特有的且常用的方法,在 ValueAnimator
中已介绍的方法便不再解释,直接使用。
ValueAnimator
的 ofXXX()
方法较多,而且有许多重载的,显然不可能都介绍,依然挑常用的讲,明白了一个,其他的也就明白了,思想都一样。
3.2.1 使用 ObjectAnimator 实现补间动画的效果
下面,我们就借助 ObjectAnimator.ofFloat(...)
方法,实现补间动画所支持的四种动画效果。
1. 平移
3s内,水平方向上向右平移300像素,然后逆向重复
效果如下:
2. 旋转
注意看图中注释。
现在,分别尝试不同的旋转参考轴,看一下效果。
rotation
:
rotationX
:
rotationY
:
3. 透明度
效果如下:
4. 缩放
效果如下:
3.2.2 解释参数问题
以上借助 ObjectAnimator
实现了补间动画的所有效果,且都是借助 ObjectAnimator.ofFloat(Object target, String propertyName, float... values)
方法,是不是很简单?这个方法是我们常用的,至于其他方法,在后面的文章中再提及。
继续说 ofFloat(Object target, String propertyName, float... values)
方法,其第一个参数 targe
和 第三个参数 values
都好理解,唯一让人困惑的是第二个参数是 propertyName
,到底应该传入什么?看着好像是直接操作属性啊,没错,属性动画当然是传属性。可是我们在 View 类中根本找不到这个属性,这就奇怪了…
如果你有这样的困惑,那我想你大概是忘记了在文章开头介绍 ObjectAnimator
时,提到的 setter
和 getter
方法了吧?ObjectAnimator
真正操作的是方法,而不是属性。拿 scaleX
举例,其对应的 getter
和 setter
方法就是 getScaleX
和 setScaleX
,不信你可以在 View 中查找一下该方法,果然有,其他属性也是这个道理,自己找找看吧。更多动画操作等着你去发现,希望大家能够灵活运用其中的方法,这样才能做出炫酷吊炸天的效果。
3.2.3 填坑
记得文章开头强调过:属性动画已不再针对View设计,而是更偏向于对值进行动画变换,可以操作任何对象的任意属性
!下面我来解释一下:
根据前面的栗子,可以看出 ValueAnimator
只是针对值进行动画,说其不针对 View 毫不违和。
那么 ObjectAnimator
又怎么体现呢?其实,这个不难看出,我们查看一下 ObjectAnimator.ofXXX(...)
方法的参数,发现所有要求传入的目标对象 target
均没有限定为 View
类型,反而是 Object
或者 泛型T。这种解释方式简单且说服力强,但不太好理解,下面,举个栗子:
定义一个普通的汽车类 Car
(该类不是 View类的子类),然后通过 speedAnim()
方法,对 Car
对象进行动画操作,模拟汽车加速的过程:
我们运行动画,看一下打印的 Log:
Log显示的速度值,显示了汽车匀加速的过程(由左侧刷新时间间隔可知,动画刷新频率有波动,导致速度的变化看上去并不是匀加速,但实际上是匀加速过程),我想这算是一个动画吧?重点是我们此处传入的 target
只是普通的 Car
,而不是 View
及其子类。别忘了得瑟一下,因为这个是补间动画所无法实现的哈哈。
此处为了演示我只是将速度打印出来,其实我们可以利用这个 speed
值结合 View 做一些动画效果,我就不再延伸了。OK,求证完毕!
不知道你想过没有,上图的log是如何打印出来的?因为在代码中并没有直接调用 setSpeed(float speed)
方法,但控制台却打印出了Log。回顾一下前面,相信你一定明白了,一不小心证明了 ObjectAnimator
是依赖于 setter
和 getter
方法完成动画的,一石二鸟。
相信到这儿,你应该相信属性动画已不再针对 View 设计,以及 ObjectAnimator
是依赖 getter
和 setter
方法实现动画的说法了吧。
3.2.4 继续填坑
且慢,又有人说话了!根据上述代码及log,只能证明 ObjectAnimator
只是借助了 setter
方法,哪有 getter
方法的事?很明显 getSpeed()
方法里面的 log 都没打印!额,好像是啊,差点被我蒙混过关,幸好被你发现了,既然这样,继续圆场吧,仔细看哈。
我们修改上述第95行代码为:
ObjectAnimator speedAnim = ObjectAnimator.ofFloat(car, "speed", 20f);
改动很小,去掉了一个参数”0f”,再运行打印一次log:
上图红色框圈中的部分,是不是 getSpeed()
方法所打印的内容?看来 getter
方法确实被调用了,这时候我们顺其自然想到是因为少传了那个“0f”,看来姿势已经对了,但具体是为什么呢?
回想一下,在第一遍模拟加速,以及在前面对 View
进行各种动画时,我们都在属性后面设置了两个以上的可变参数,但这一次是一个参数。或许你会想到在前面介绍 ValueAnimator
时,我们通过源码证明了当只有一个参数时,系统会直接设开始值为其数据类型的默认值,float 的默认值是 0f,恰好Log里面打印的也是 0.0,原来如此!
但我想说你理解错了!因为,如果像 ValueAnimator
一样直接设默认值,却不是调用 getSpeed()
方法,为啥会有第一句的日志?原来,当我们使用 ObjectAnimator
且只传一个值时,ObjectAnimator
会先主动去调用对象的 getter
方法获取开始值,只有当 getter
不存在时,才会像 ValueAnimator
那样直接设默认值。所以,对于 ObjectAnimator 来说,getter 方法是视情况调用的,但 setter 方法却一定会被调用。
3.2.5 延伸:通过源码了解 ObjectAnimator 如何使用 getter 和 setter 方法
本部分属于延伸内容,旨在通过源码了解 ObjectAnimator
是如何调用对象的 getter
和 setter
方法的,对源码不感冒的也建议看一下,因为我已经使劲洪荒之力,将源码翻译成大白话了哈哈。为方便理解,我将源码提取出来进行了适当删减,并翻译、添加了注释,若有不对的,还望指正。
ObjectAnimator
获取属性对应的 getter
或 setter
方法时,会分别调用 PropertyValuesHolder
类的如下方法:
由上述圈中的代码可知,无论获取 getter
还是 setter
方法, 都会走到 setupSetterOrGetter(...)
方法中,来获取对应的方法,代码如下。该方法引入了缓存机制,感兴趣的可以看一下注释,这个缓存机制值得学习,我在这就不再补充。
观察上述代码,我们发现如果是第一次查找某个属性对应的方法,缓存肯定不存在,该怎么办?我们注意到上图红框圈中的部分,也就是当缓存不存在时,会调用 getPropertyFunction(targetClass, prefix, valueType)
方法从目标Class对象中直接获取方法,获取成功过后再加入缓存方便下次直接使用。获取属性对应方法的代码我们跟进去看一下,如下图。
图中注释已经很清楚了,这儿再补充两点。
第一:在方法最后我们发现,如果我们传入属性之后,系统没有找到对应的 getter
或 setter
方法时,其并没有报错进而崩溃,只是打印了错误原因,这块大家可以自己尝试注释相应的 getter
或 setter
方法验证一下,看有没有打印这句话,我就不举栗子啦。
第二:该方法的两个关键作用是分别依靠 PropertyValuesHolder
类的 getMethodName(...)
方法和 Class
类的 getMethod(...)
方法实现的,下面我们贴一下这两个方法的代码,大家自行感受一下,看图不解释了:
[打广告:有关反射的知识,可以参考我之前写的两篇博客,点这儿]
看完这个延伸学习,是不是感觉像喝了脑白金一样,思路超级清晰?那就好!但是现在正在写博客的我,有点头疼,因为还有一个问题困惑着我,我在这儿写出来,有知道的同学还望指导一下:
根据我的理解,在 getMethod(…) 方法中,如果当前要找的 getter 或 setter 方法是私有的,则会抛出异常,进而会在上一级方法中捕获,并最终打印错误日志。根据这个思路,我将
getter
方法设置为private的,确实像我分析的那样打印了错误日志,提示没有找到方法;但是,当我将setter
方法设置为私有时,却没有打印错误日志,动画还能正常运行!Why?
最后,总结一下有关 setter
和 getter
的东西:
1 - 只要有对应的 setter
方法存在,即使没有传入初始值、没有 getter
方法,动画都能正常运行,原因前面说了,系统会为初始值赋值,但这时依然会打印没有找到 getter
方法的日志;
2 - 反之如果没有 setter
方法,即使有初始值 或 getter
方法,动画都无法正常运行,因为没法将变化后的值赋给对应的属性。后果只是动画不执行,然后打印没有找到 setter
方法的日志,程序不至于崩溃;
3.3 针对 ValueAnimator 和 ObjectAnimator 的一点补充
至此,大家对 ValueAnimator
和 ObjectAnimator
有了大概的了解: ValueAnimator
针对值进行变换,而 ObjectAnimator
是对 ValueAnimator
的扩展,会在对值进行变换的基础上,自动调用任意对象 target
的 getter
和 setter
方法,对传入的任何属性 propertyName
进行动赋值,进而产生动画效果。值得注意的是,对于 ObjectAnimator
,我始终没有提及 View
对象,只是说任意对象的任意属性。
这么说,二者最大的区别就在于 target
的 getter
和 setter
方法了。既然如此,那么上面借助 ObjectAnimator
实现的汽车加速的栗子,是不是可以使用 ValueAnimator
来实现呢?答案是可以的,代码如下:
打印结果就不附图了,和之前完全一样。此处使用 ValueAnimator
实现汽车加速效果,其本质就是对值进行操作,然后将 ObjectAnimator
封装的调用 getter
和 setter
方法的逻辑,抽出来自己实现:在开始时通过 getSpeed()
拿到初始值,并在动画过程中通过监听获取实时的 speed
值,调然后用 setSpeed(float speed)
方法将值设置进去。其中精髓你体会到了吗?
我之所以加这个补充,就是想大家都可以再一次融会贯通,举一反三,相信你会在某一刻醍醐灌顶!
结束
基础篇差不多就到这儿了,还有使用 AnimatorSet
实现组合动画,以及在 XML 中使用组合动画,原打算在本篇讲的,然后下篇讲一下 TypeEvaluator
和 插值器TimeInterpolator
,这样两篇结束。但随着研究的深入,发现要写的远比计划的多了去了,为了将属性动画写个明白,我还是毫无保留的都写进来了,这样的话就要多写几篇了,具体的后续会更新,尽快更完吧。
因为属性动画算是比较大的一块,需要对整体有清楚的认识才能写好。尽管我已经很努力的在写,尽可能让大家更容易理解,但真正写的时候,难免会出现思路和表达上的问题,或者遗漏了什么,所以恳请大家在阅读、学习的过程中,发现有什么问题、或者好的想法,能够反馈给我,我会及时完善更新,也欢迎一起交流学习!
扫描下方二维码,关注我的公众号,及时获取最新文章推送!