Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

剖析 Android 属性动画,知其然知其所以然(上)

$
0
0

转载请注明出处:http://blog.csdn.net/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(值)进行动画变换,但它却是属性动画的核心类,后面要介绍的 ObjectAnimatorValueAnimator 基础上进行的二次封装。

2.3 final class ObjectAnimator

继承关系:ObjectAnimator extends ValueAnimator
作用:本类为 ValueAnimator 的子类,支持对目标对象的属性进行动画(animating properties on target objects)。这个类的构造函数,通过参数定义需要进行动画操作的目标对象,以及动画所针对的属性名称。确定对象内部存在合适的 gettersetter 方法,然后动画将在必要时调用这些方法以对属性进行动画。
其他:该类使用 final 修饰,无法再被其他类继承;其直接对目标对象的具体属性进行动画操作,前提是该属性存在相应的 settergetter 方法。

2.4 AnimatorSet extends Animator

继承关系:AnimatorSet extends Animator
作用:本类可以在特定的顺序下播放一组 Animator 对象动画(plays a set of Animator objects in the specified order)。动画可以被设置为同时播放、异步播放或者在特定的延时之后播放。
其他:AnimatorSet 操作的对象为 AnimatorValueAnimatorObjectAnimator 都是 Animator 的子类,所以都可以借助 AnimatorSet 实现组合动画。

有了以上几个类(实际常用的是 ObjectAnimator 和 AnimatorSet),我们就可以使用属性动画基本的功能实现大多数动画效果啦,想想都很激动,下面就开始正式介绍基础篇的内容。

3. 基础部分

下面将重点介绍几个类的用法,由于在 API 一览中已经对各个类进行了简单的介绍,所以接下来就直奔主题,上代码。

3.1 ValueAnimator

其为属性动画的核心类,但实际使用中,我们一般很少与其直接打交道,但不代表我们不需要了解他的用法。一般情况下,我们都会使用其提供的静态方法 ofXXX() 实现相应的值动画,包含以下几个方法:

ValueAnimator - ofXXX

下面重点讲解一下 ValueAnimator.ofFloat(float... values) 的用法:

1. 基本用法

实现在100ms内,将一个值从0过渡到1,并打印出来:

ValueAnimator - ofFloat

打印如下,默认刷新频率为10ms,但实际频率视实际情况有所波动:

打印

OK,成功的将值打印出来了,是不是很简单。但是细心地你会发现,值得变化并不是均匀的,明显感觉值是先加速增长,后减速增长。确实是这样,对属性动画有一定了解的或许知道插值器(interpolator)这个东西,不知道的也没关系,下篇会介绍。在这儿你只需知道,属性动画默认使用了先加速后减速的插值器(注释文档: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 减轻压力,因为 ObjectAnimatorValueAnimator 子类,ValueAnimator 的方法在 ObjectAnimator 中同样适用。下面着重介绍 ObjectAnimator 所特有的且常用的方法,在 ValueAnimator 中已介绍的方法便不再赘述。但其 ofXXX() 方法较多,而且有许多重载的,不可能都介绍,依然挑常用的讲,大家要学会举一反三。

3.2.1 使用 ObjectAnimator 实现补间动画的效果

1. 平移

3s内,水平方向上向右平移300像素,然后逆向重复

ObjectAnimator - translate

效果如下:
ObjectAnimator - translate

2. 旋转

注意看图中注释。

ObjectAnimator - 旋转

现在,分别尝试不同的旋转参考轴,看一下效果。

rotation:
ObjectAnimator - rotation
rotationX:
ObjectAnimator - rotationX
rotationY:
ObjectAnimator - rotationY

3. 透明度

透明度

效果如下:
ObjectAnimator - alpha

4. 缩放

缩放

效果如下:
ObjectAnimator - scale

3.2.2 说一说参数问题

以上借助 ObjectAnimator 实现了补间动画的所有效果,且都是借助 ObjectAnimator.ofFloat(Object target, String propertyName, float... values) 方法,这也是我们常用的。至于其他方法,大家可以自己动手试一下,就不再一一列举,用法都差不多,在进阶篇中还会再提及部分方法的使用。继续说 ofFloat(Object target, String propertyName, float... values) 方法,其第一个和第三个参数都好理解,唯一让人困惑的是第二个参数是 String 类型,到底应该传入什么?看着好像是直接操作属性啊,没错,属性动画当然是操作属性。可是我们进入View 类中并没有发现这个属性,这就奇怪了…

我想,你大概是忘记了在文章开头介绍 ObjectAnimator 时,提到的 settergetter 方法了吧?ObjectAnimator 真正作用的是 方法,而不是直接操纵属性。拿 scaleX 举例,其对应的 gettersetter 方法就是 getScaleXsetScaleX,不信你可以在 View 中查找一下试试。同理,对应的还有 setTranslationX | getTransLationsetAlpha | getAlpha 等。更多动画操作等着你去发现,希望大家能够灵活运用其中的方法,这样才能做出炫酷吊炸天的效果。

3.2.3 填坑

记得文章开头强调过:属性动画已不再针对View设计,而是更偏向于对值进行动画变换,可以操作任何对象的任意属性

ValueAnimator 只是针对值进行动画,说其不针对 View 毫不违和。那么 ObjectAnimator 又怎么体现呢?其实,从 ObjectAnimator.ofXXX(...) 方法的参数我们就可以看出来,所有要求传入的目标对象 target 均没有限定为 View 类型,反而是 Object 或者 泛型T。我想这样说还是不太好证实,下面,举个栗子:

定义一个普通的汽车类 Car(该类不是 View类的子类),然后通过 speedAnim() 方法,对 Car 对象进行动画操作,模拟汽车加速的过程:

Car

首先,我没没有在代码中显示的调用 setSpeed(float speed) 方法,但控制台却打印出了Log(下图),再一次证明了 ObjectAnimator 作用于 settergetter 方法。

当然,我们关注的重点还是是否实现了动画,那么看一下打印的 Log:

ObjectAnimator - Car-log

借助Log显示的速度值,显示了汽车匀加速的过程(由左侧刷新时间间隔可知,动画刷新频率有波动,导致速度的变化看上去并不是匀加速,但实际上是匀加速过程),也算是一个动画吧?要知道这个是补间动画所无法实现的。

此处为了演示我只是将速度打印出来,其实我们可以利用这个 speed 值做一些有意义的事,我就不再延伸了。再次强调一下,ofFloat(...) 中传入的 Car类不是 View 的子类。OK,求证完毕!

相信到这儿,你应该明白属性动画已不再针对View设计,以及 ObjectAnimator 是借助getter 和 setter 方法实现动画的说法了吧。

3.2.4 继续填坑

且慢,又有人说话了!根据上述代码及log,只能证明 ObjectAnimator 只是借助了 setter 方法,哪有 getter 方法的事?很明显 getSpeed() 方法里面的 log 都没打印!额,好像是啊,差点被我蒙混过关,幸好被你发现了,既然这样,继续填坑吧圆场吧。

接下来,我们修改上述第95行代码为:

ObjectAnimator speedAnim = ObjectAnimator.ofFloat(car, "speed", 20f);

改动很小,去掉了一个参数”0f”,再运行打印一次log:

ObjectAnimator - Car-log

上图红色框圈中的部分,是不是 getSpeed() 方法所打印的内容?看来确实起作用了,这时候我们顺其自然想到是因为少传了那个“0f”,看来姿势已经对了,但具体是为什么呢?

在第一遍模拟加速,以及在前面对 View 进行各种动画时,我们都在属性后面设置了两个以上的可变参数,但这一次是一个参数。这时候,我们又会联想到在介绍 ValueAnimator 时一个参数的情况,根据源码可知系统会默认开始值为0f,恰好Log里面打印的也是0,原来是这样!

但仔细一想不对,如果像 ValueAnimator 一样直接设默认值,而不是调用 getSpeed() 方法,为啥会有第一句的日志?原来,当我们使用 ObjectAnimator 且只传一个值时,ObjectAnimator 会主动去调用对象的 getter 方法获取开始值,而不是像 ValueAnimator 那样直接设默认值。所以,对于 ObjectAnimator 来说,getter 方法是视情况调用的,但 setter 方法却一定会被调用

3.2.5 延伸:通过源码了解ObjectAnimator如何使用 getter 和 setter 方法

本部分属于延伸内容,旨在通过源码了解 ObjectAnimator 是如何调用对象的 gettersetter 方法的,对源码不感冒的可以直接略过,但我还是建议大家看一下。为方便理解,我将源码提取出来进行了适当删减,并翻译、添加了注释,若有不对的,还望指正

ObjectAnimator 获取属性对应的 gettersetter 方法时,会分别调用 PropertyValuesHolder 类的如下方法:

源码分析-1

由上述代码可知,无论获取 getter 还是 setter , 都会走到 setupSetterOrGetter(...) 方法中,来获取对应的方法,代码如下。该方法引入了缓存机制,感兴趣的可以看一下注释,不再补充。

源码分析-2

观察上述代码,我们发现如果是第一次查找某个属性对应的方法,缓存肯定不存在,该怎么办?我们注意到上图红框圈中的部分,也就是当缓存不存在时,会调用 getPropertyFunction(targetClass, prefix, valueType) 方法从目标Class对象中直接获取方法,获取成功过后再加入缓存方便下次直接使用。获取属性对应方法的代码我们跟进去看一下,如下图。

源码分析 -3

图中注释已经很清楚了,再补充两点。

第一:在方法最后我们发现,如果我们传入属性之后,系统没有找到对应的 gettersetter 方法时,其并没有报错进而崩溃,只是打印了错误原因,这块大家可以自己尝试注释相应的 gettersetter 方法验证一下,我只提个醒。

第二:该方法的两个关键作用分别依靠 PropertyValuesHolder 类的 getMethodName(...)Class 类的 getMethod(...) 方法实现的,下面我们贴一下这两个方法的代码,大家感受一下:

源码分析 -4

[打广告:有关反射的知识,可以参考我之前写的两篇博客,点这儿]

现在思路是不是超级清晰?那就好!但是现在正在写博客的我,有点头疼,因为还有一个我没有解决的问题,我在这儿写出来,有知道的同学还望指导一下:

根据我的理解,在 getMethod(…) 方法中,如果当前要找的 getter 或 setter 方法是私有的,则会抛出异常,进而会在上一级方法中捕获,并最终打印错误日志。于是我将 getter 方法给位私有private的,确实像我分析的那样;但是,当我将 setter 方法设置为私有private时,却没有打印错误日志,动画还能正常运行!Why?

最后,总结一下有关 settergetter 的东西,其实就两点:

1 - 只要有对应的 setter 方法存在(即使没有传入初始值、没有 getter 方法),动画就能正常运行;反之只要没有 setter 方法,动画就一定无法正常运行,因为没法将值赋给对应的变量。
2 - 注意图中圈中部分,没有传入初始值、没有 getter 方法,只有 setter 方法,根据第1点动画正常运行。可为什么正常运行的同时还打印错误?因为在没有初始值时,系统会去找对应的 getter 方法,发现没有找到时,会打印错误日志。最后,在获取无果的情况下,系统将初始值赋为对应数据类型的默认值。

3.3 针对 ValueAnimator 和 ObjectAnimator 的一点补充

至此,大家对 ValueAnimatorObjectAnimator 有了大概的了解,ValueAnimator 针对值进行变换,而 ObjectAnimator 是对 ValueAnimator 的扩展,会自动调用任意对象的 gettersetter 方法对任何属性进行动画操作,重点就在于这个 gettersetter 方法,而且我们始终没有提及 View 对象,只是说任意对象的任意属性

其次,上面借助 ObjectAnimator 实现的汽车加速的栗子,可以使用 ValueAnimator 来实现,代码如下:

ValueAnimator实现ObjectAnimator的car

打印结果就不附图了,和之前完全一样。此处使用 ValueAnimator 实现汽车加速效果,其本质就是对值进行操作,然后将 ObjectAnimator 封装的动画细节抽出来自己实现了:在开始时通过 getSpeed() 拿到初始值,并在动画过程中通过监听获取实时的 speed 值,调然后用 setSpeed(float speed) 方法将值设置进去。其中精髓你体会到了吗。

我之所以加这个补充,就是想大家都可以再一次融会贯通,举一反三。其实,对 View 动画也可以使用 ValueAnimator 实现哦,你懂得!只不过一般我们不那么做(废话,哈哈)。

结束

基础篇差不多就到这儿了,还有使用 AnimatorSet 实现组合动画,以及在 XML 中使用组合动画,本打算在本篇讲的,然后下篇讲一下 TypeEvaluator 和 插值器TimeInterpolator,这样两篇结束。但随着研究的深入,发现要写的远比计划的多了去了,决定多写几篇,具体的后续会更新,争取在一周内更完。

因为属性动画算是比较大的一块,需要对整体有清楚的意识才能写好。尽管我已经很努力的在写,尽可能让大家更容易理解,但难免会出现思路和表达上的问题,或者遗漏了什么,所以恳请大家在阅读、学习的过程中,有什么问题、或者好的想法,能够反馈给我,我会及时完善更新!如果觉得本文对你有帮助,可以点赞表示一下哈哈!

作者:My_TrueLove 发表于2016/8/21 18:31:19 原文链接
阅读:67 评论:1 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>