导读
在文章开头我们先简单回忆一下常用的属性动画 API 有哪些?
1.动画相关的类:ObjectAnimator,ValueAnimator,Property,PropertyValuesHolder,Keyframe,Keyframes,TimeInterpolator,TypeEvaluator
在剖析过程中我们会解析相关类的作用和实现方式
2.常用的 属性动画 方法:
1 | ObjectAnimator.ofInt() |
下面我们以一个常用的方法 ObjectAnimator.ofInt() 为切入点层层深入解析属性动画的创建过程。
动画的创建
早期版本的属性动画 ofInt 方法有两个实现,到Android O 为止 已经有 4 个实现了。
主要就是增加了 Path 参数。我们这里并不去解析新方法,因为它的实现方式和最基本的方法相同。
1 | public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { |
注意:我这里以Android-25 源码为代码源。
上述代码中ofInt 方法的第一行均是 创建 ObjectAnimator 对象。但分别使用了两种构造方式。
下面我们探索一下 ObjectAnimator 的构造方法:
1 | //无参构造 |
无参构造方法没有什么可说的。另外两个构造方法第一行代码都是
1 | setTarget(target); |
该setTarget方法的作用是记录需要做动画的对象,方便以后对该对象的相关属性做更改。
1.我们首先看看 propertyName 参数的构造方法的setPropertyName 方法
1 | public void setPropertyName(@NonNull String propertyName) { |
我们这里的调用情形,mValues肯定为null,不会走 if分支,剩下的就是简单的记录我们设置的propertyName用mPropertyName保持。如果mValues不为空的情况下,会更新 mValues的第一个元素的属性值,并更新对应的map集合
2.我们再看看Property参数的构造方法的setProperty方法,同样也是简单的记录mProperty和mPropertyName
那么 这个Property是何方神圣?
Property 解析
1 | public abstract class Property<T, V> { |
这是一个 abstract修饰的类。这个类很简单,就是定义了一些需要被子类实现的方法。(为了减小博客的字数,我这里隐去源码中的相关注释,如有需要请自行到源码中查看)
通过对注释的理解我们知道 这个类的注意功能就是代表需要动画的属性,实现这个类有一些先决条件需要满足:
必须满足一下三个条件中的一个
1.被 Property 代表的类的属性必须有一个public的无参的get方法,和一个可选的set方法,set方法的参数和get方法的返回值一个类型
2.有一个public的无参的is 方法,和一个可选的set方法 set方法的参数和is方法的返回值一个类型
3.一个public的 属性 name。
假如有 get/is方法,但是没有set方法,那么这个 Property 是一个只读的属性。set 方法必须实现,都在抛出异常
到目前为止我们了解了 动画初始化时会初始化 mPropertyName变量,记录属性的名称;或初始化mProperty,来保持对属性的访问能力和更新能力。
这里说明一下,常规情况下需要满足这三个中一个即可,如果我们又其他方式来变量属性值,而且也在 Property 中做了相应的映射关系,不满足也可以。这个在之后文章中自定义属性动画中可以看出
我们回到之前的 ofInt方法
紧接着 执行了 代码
1 | anim.setIntValues(values); |
这行代码看似简单,其实包含了很多信息。从字面意思理解,就是保存动画的属性值范围。
1 |
|
第一次调用 mValues为 null走入if流程,假如我们调用的 propertyName参数的方法,会执行setValues(PropertyValuesHolder.ofInt(mPropertyName, values));,反之执行setValues(PropertyValuesHolder.ofInt(mProperty, values));
我们先看看PropertyValuesHolder.ofInt(mProperty, values)
1 | public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) { |
该方法主要的作用就是创建一个PropertyValuesHolder,保持属性Property
并将 属性值保存成关键帧,我们关注核心如何生成关键帧
1 | public static KeyframeSet ofInt(int... values) { |
上述代码是生成关键帧的逻辑:
1.判断值的个数,如果只有一个值。那么分配长度为2的IntKeyframe数组,并将值保持在第二个位置。
2.如果多于一个值,会分配 值个数 长度的数组,将每一个数值都保持在数组对应的位置。
需要注意的是:除了第一帧,其他帧都分配了fraction,这个系数有这些数字的传入顺序决定。假如我们传入3个值,第一帧的系数默认为0,第二帧的系数为0.5,最后一个值得系数是1
。这个系数在后来动画的执行中为生成相应的属性值起到关键作用。
到目前为止,我们知道PropertyValuesHolder持有了Property并将动画的值保存成关键帧,并持有这些关键帧。
同样setValues(PropertyValuesHolder.ofInt(mPropertyName, values));方法也相同的方式生成了PropertyValuesHolder并持有 生成的关键帧
然后我们分析一下setValues方法。
这是父类 ValueAnimator的方法
1 | public void setValues(PropertyValuesHolder... values) { |
主要功能:
1.保存前面生成的 PropertyValuesHolder,用 mValues持有引用。
2.将 PropertyValuesHolder数组按照属性名为key,PropertyValuesHolder为值保存至map中,并标记为初始化mInitialized = false,在下一篇文章中会提到何时将标记位mInitialized置为 true
到目前为止我们已经分析完了 ObjectAnimator.ofInt方法。
其中有几个未解释到的类:
1.PropertyValuesHolder
2.Keyframe
PropertyValuesHolder
由前面的分析我们大致知道 PropertyValuesHolder 持有了 Property或mPropertyName,以及持有 关键帧。
除此之外,它还有 属性mSetter,mGetter,mEvaluator1
2
3Method mSetter = null;
private Method mGetter = null;
private TypeEvaluator mEvaluator;
以及一堆 of方法
ofInt
ofMultiInt
ofFloat
ofMultiFloat
ofObject
ofKeyframe
这些 of方法我们前面解析过 ofInt 这里不再展开解释。
看到 mSetter和mGetter方法,我们联想到这个类肯定会操作 动画对象的属性。果不其然,让我们找到了 setupSetter和setupGetter 方法
1 | void setupSetter(Class targetClass) { |
这两个方法通过 拼接set 和get,通过反射获取属性的 get set 方法,从而获得对象属性的控制权。
当然 一些PropertyValuesHolder的子类重载使用了 jni 方法获得反射方法。其实都是目的都是一样。
当然,获得了这些属性的控制权,自然会更新这些属性
1 | void setAnimatedValue(Object target) { |
这个 setAnimatedValue方法就是用于更新这些属性;如果有 mProperty就通过 Property更新属性,如果没有的话,就通过反射更新。
当然在更新属性之前需要计算出正确的属性值。
1 | void calculateValue(float fraction) { |
calculateValue方法就是用于计算这些值。计算又用到了Keyframes,下面分析关键帧相关的API。到此为止我们理解了 PropertyValuesHolder的作用
总结一下:
1.持有 Property 用于获取,更新属性的能力。
2.持有 mPropertyName 通过反射获取get , set 方法,获取更新属性的能力
3.计算属性的值
4.更新属性
所以 PropertyValuesHolder就是控制动画对象属性的关键。所以也很容易理解,为什么所有的属性动画的value都转化成PropertyValuesHolder来操作.
Keyframe
下面我们在详细了解下何为关键帧,它是如何运作的?
先看几个关于关键帧的类 Keyframe,Keyframes 以及它们的子类Keyframe顾名思义就是记录关键帧数据,他默认有三个实现类IntKeyframe,FloatKeyframe,ObjectKeyframe,当然我们也能继承Keyframe实现自己的关键帧类型。不过大部分情况,提供的这三种方式已经够用了。Keyframe 有三个重要的属性值:
1.mHasValue 用于记录关键帧是否初始化了值,以 子类 IntKeyframe 为例,它有两个构造方法:
1 | IntKeyframe(float fraction, int value) { |
第一个构造初始化了 mValue ,同时 mHasValue会标记 为true;
第二个构造方法 只初始化了 mFraction,并未给 mValue赋值,默认 mHasValue为false,假如我们使用了mHasValue为 false的 关键帧。那么在动画初始化时会调用PropertyValuesHolder.setupSetterAndGetter给每一个关键帧赋初始值。
2.mFraction记录该关键帧所有关键帧中的位置,float类型,值范围0 - 1
3.mValue记录value值,当然不同类型的Keyframe value值类型也不同,所以 mValue由子类实现
Keyframes
我们可以认为 Keyframes是一堆 Keyframe组成的集合。
先看看继承体系:
它的实现类有KeyframeSet,PathKeyframes,它的子接口 有IntKeyframes,FloatKeyframes
我们看看常用的KeyframeSet,继承它的两个子类IntKeyframeSet,FloatKeyframeSet,通知实现了对应的IntKeyframes和FloatKeyframes接口。
其实 Keyframes最核心的方法是getValue方法,其子实现均是为了实现它。下面是KeyframeSet的getValue方法的实现:
1 | public Object getValue(float fraction) { |
该方法核心是 通过参数fraction(时间流逝比)来计算对应的属性值,计算出来的值如无TypeConverter转换需求,那就会把这个值设置到 动画Target的属性上,从而实现属性动画对属性的更改。
观察到这里还使用了一个TypeEvaluator,这哥们大家估计通过其他途径都了解过,我们经常把 TimeInterpolator和TypeEvaluator一起来解释属性动画的原理,即插值器和估值器对属性动画的作用。我们这里不展开讨论,只基于源码去分析。
我们先看第一个if分支
1 | if (mNumKeyframes == 2) { |
对应当仅有两个关键帧时的情况。举个例子,假如我们调用ObjectAnimator.ofInt方法只传入了一个int值,这里的 mNumKeyframes会是2,而且看注释,只有在这种情况下才使用mInterpolator-插值器。通过插值器 得到修正后的系数fraction,然后拿这个系数,通过估值器mEvaluator计算出属性值。我们发现估值器是何等的重要,必须要被赋值的,否则无法计算出属性值。
继续 if (fraction <= 0f)和if (fraction >= 1f)这两个分支是特殊情况,出现在动画第一帧和最后一帧的情况。
下面看看正常动画中的分支流程:
1 | Keyframe prevKeyframe = mFirstKeyframe; |
通过fraction查找符合fraction < nextKeyframe.getFraction()的第一帧,然后计算 fraction在前一帧到当前帧范围内的位置,例如查找到符合条件的当前帧 fraction =0.5;前一帧fraction = 0.2, 动画当前的 fraction =0.3,那么新的 intervalFraction = 1/3f。然后拿着这个值给估值器计算属性值。
到目前为止我们知道了,关键帧的作用如下:
1.保存每个关键帧的值
2.保存每个关键帧的 fraction
3.通过动画类传入的 fraction,利用估值器计算出 真正的属性值。
那么剩下的就是如何将这些计算出来的属性值设置到对应的Target上了。这个问题我们下一篇分析动画的启动原理时会再分析。
回顾
1.以ObjectAnimator.ofInt为切入点 分析 ObjectAnimator对象的创建
2.属性名称propertyName和Property的作用:
propertyName 用于拼接 get/set方法,获得控制属性的能力;
Property 利用自己的get和set方法直接控制属性。
PropertyValuesHolder的作用:通过 propertyName 或 Property 获取更新属性的能力;根据动画时间流逝比计算属性的值;更新属性值到对应的Target上- 关键帧
keyframe用于保存动画的每一个关键数据帧,个数由传入的 value 个数决定,系数 fraction 由 value 传入顺序决定。 keyframes以及它的子类KeyframeSet的作用:利用估值器和 fraction 计算出应该更新到Target上的属性值。
基本API 解析完毕,下一篇我们会分析动画如何开始的,fraction系数如何得到的,以及何时更新 属性值。