属性动画简单分析
Posted chunqiuwei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了属性动画简单分析相关的知识,希望对你有一定的参考价值。
在《属性动画简单解析(一)》分析了属性动画ObjectAnimation的初始化流程:
1)通过ObjectAnimation的ofXXX方法,设置propertyName和values。
2)将propertyName和values封装成PropertyValueHolder对象:每个PropertyValueHolder对象持有values组成的帧序列对象KeyFrameSet对象;
3)将步骤2创建的PropertyValueHolder对象用ObjectAnimation的mValues 数组保存起来;并用map缓存,最终可以用如下图片来表示初始化的最终完成结果:
一切就绪后,我们就可以调用ObjectAnimation对象的start方法来启动动画了!
需要注意的是本片博客继续沿袭第一篇博客的说明,本篇所讲的KeyFrame实际上是ObjectKeyframe
结合《属性动画简单说明前篇》这篇博文和上图,不难理解属性动画的执行逻辑如下:start方法开启之后,根据当前时间计算此时的fraction属于KeyFrameSet中的哪一个KeyFrame(currentFeyFrame),然后,然后根据将当前fraction 、preKeyFrame.value和nextKeyFrame.value三者交给TypeEvaluator的计算出一个值,然后将该值通过反射调用Objectde target方法,然后循环遍历下一时刻的fraction,并重复TypeEvaluator的计算过程即可,注意fraction的范围仍然是[0,1]。
还是用代码说话吧,从ObjectAnimator的start方法开始:该start方法直接调用了父类ValueAnimator的start(),VualeAnimator调用了start(boolean playBackwards) 方法;下面基本上就是对源码的分析了,源码分析结束后会会总结成一张图出来方便理解,当然如果对源码没兴趣的话,也可以直接看下文的图来理解之。
private void start(boolean playBackwards) {
//将当前Animator对象放入一个集合中
sPendingAnimations.get().add(this);
//此处省略部分代码
if (mStartDelay == 0) {
setCurrentPlayTime(getCurrentPlayTime());
//此处省略部分代码:
}
//此处省略部分代码
//发送一个消息
animationHandler.sendEmptyMessage(ANIMATION_START);
}
start方法很简单,显示调用了setCurrentPlayTime然后发送一个handler,那么继续追踪setCurrentPlayTime方法:
public void setCurrentPlayTime(long playTime) {
//此处省略部分代码
initAnimation();
//此处省略部分代码
animationFrame(currentTime);
}
上面代码调用了initAnimation和animationFrame方法,那么就来按顺序看看他们都干了些什么事儿,注意因为分析的是ObjectAnimator这个对象,所以initAnimation应该看ObjectAnimator类里面的方法,而不是ValueAnimator的方法:
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(mTarget);
}
super.initAnimation();
}
}
//super.initAnimation();ValueAnimator的方法
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
initAnimation方法其实做了两个逻辑:
1)调用为setupSetterAndGetter方法为ObjectAnimator中mValues数组中的每一个PropertyValuesHolder赋值,参考上图确切的来说就是为PropertyValuesHolder对象所持有的mKeyframeSet中每一个KeyFrame赋值,如果KeyFrame在初始化的时候没有初始值就将目标对象的get方法为其设置初始值。具体代码如下:
void setupSetterAndGetter(Object target) {
try {
Object testValue = mProperty.get(target);
//遍历PropertyValuesHolder所持有的mKeyframeSet
for (Keyframe kf : mKeyframeSet.mKeyframes) { //如果没有设置初始值的话就设置初始值
if (!kf.hasValue()) {
kf.setValue(mProperty.get(target));
}
}
return;
} catch (ClassCastException e) {
}
}
//此处省略部分代码
}
2)调用super.initAnimation为上图中的每个PropertyValuseHodler设置插值器TypeEvaluator
到此为止setCurrentPlayTime中调用的initAnimation方法讲解完毕,那么此时上图的中完成的初始化工作可以简单如下所示:
执行完initAnimation后,setCurrentPlayTime继续执行animationFrame方法:
(事先透露一句:该方法正式开始了属性动画的核心:根据当前fraction的值,根据上文设置的插值器,计算目标对象的setXX方法所需参数的值,并放射调用setXX方法来逐步完成属性动画的过程)
boolean animationFrame(long currentTime) {
boolean done = false;
//省略部分代码
switch (mPlayingState) {
case RUNNING:
case SEEKED:
//fraction的计算公式为:(当前时间-开始时间)/动画时间
float fraction = mDuration > 0 ? (float) (currentTime - mStartTime)
//省略了部分代码
animateValue(fraction);
break;
}
return done;
}
为了便于博文的流程梳理,该方法在这里先不细说,其计算了fraction在最后调用了animateValue(fraction)方法,所以先来看看animateValue方法:
void animateValue(float fraction) {
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//调用PropertyValuesHolder的setAnimatedValue方法
//为target赋值
mValues[i].setAnimatedValue(mTarget);
}
}
//super.animateValue方法:
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//为每一个PropertyValuesHolder调用calculateValue方法
mValues[i].calculateValue(fraction);
}
//省略部分代码
}
animateValue方法先调用父类ValueAnimator的animateValue方法,该方法可以说是属性动画的核心算法体现所在,该方法循环上图中每个PropertyValuesHolder,根据fraction为每个PropertyValuesHolder对象调用其calculateValue方法设置此时目标对象Target的set方法当前应该传入的值,该值保存在PropertyValuesHolder对象的mAnimatedValue变量里保存(当然本篇就不另外说明calculateValue的具体计算思路了,具体思路可参考博主另外一篇博文《属性动画简单说明前篇》)。调用完父类的方法之后,PropertyValuesHolder用图来表示的话就是如下所示了(高手貌似都是喜欢用图来说话):
分析完了ValueAnimator的方法animateValue之后,继续分析ObjectAnimator的animateValue方法,会发现该方法正好与父类的方法相反,父类的方法是为PropertyValuesHolder的mAnimatedValue变量设置值,那么ObjectAnimator的方法就是为获取mAnimatedValue的值并赋值给目标对象的setXX方法。具体处理方法是循环遍历PropertyValuesHolder对象,调用其setAnimatedValue方法:
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
//getAnimatedValue
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}
}
}
setAnimatedValue方法也很简单:
1、getAnimatedValue获取mAnimatedValue保存的值
2、将mAnimatedValue的值通过反射调用目标对象的setXX方法,设置到目标对象中
所以到此为止ObjectAnimator通过OfXX设置的values,经过上述的重重调用计算,最终经过反射调用setXX方法,最终达到了修改目标对象属性的目的。
但是,It’s not over yet!到现在只是讲了一次调用setXX的过程,属性动画又是怎么逐步调用每一帧来让属性“动”起来呢?还记得文章开头说的start方法吗?最后发送了由handle发送了一个Messege方法:animationHandler.sendEmptyMessage(ANIMATION_START);,看看这个方法 是干什么呢?是不是做了下一帧调用的处理呢?
让我们看看这个Message方法都做了神马了不起的事儿!真相在一点一点打开:
先剖开别的不谈,其实阅读到这段代码的时候着实让我郁闷纠结了一段时间,因为ValueAnimator里面的handler只发送了两个消息:ANIMATION_START和ANIMATION_FRAME,其中第一个消息是在start里面发送的,但是ANIMATION_FRAME这个消息是什么时候发送的呢?看源码只有下面case语句里面一句发送了ANIMATION_FRAME消息啊:
case ANIMATION_FRAME:
sendEmptyMessage(ANIMATION_FRAME);
break;
到底是怎么handleMessage的switch(msg.what)到底是怎么走到case ANIMATION_FRAME这个分支上的呢?其实特么的有点坑爹了,那是因为处理消息的代码框架如下:
case ANIMATION_START:
doSomthing();
case ANIMATION_FRAME:
sendEmptyMessage(ANIMATION_FRAME);
break;
那就是ANIMATION_START这个case分支没有break语句,执行完ANIMATION_START分之后会直接执行ANIMATION_FRAME分支;真特么坑爹,一时没留意这单,让我着实纠结了一阵
因为本篇博文较长,所以下面应该看成是本篇博文的第二大部分:
那么正式开始吧,不过如果按照正常顺序来说明handleMessage的执行流程的话,可能会有点绕,因为它的代码有点长而且按照其流程说起来估计会对读者造成困惑,所以在这里我就先说结果,然后带着结果分析源码吧!
阅读源码可以发现:ValueAnimation提供了四个ThreadLocal类型的ArrayList静态集合:
sAnimations:包含着正在执行也即是已经执行但是还没有执行完毕的动画集合,处于此集合中的ValueAnimator或者 ObjectAnimator对象正在执行状态中
sPendingAnimations:如果一个ObjectAnimator对象调用了start()方法,那么就把此对象放入sPendingAnimations中,该集合中的动画尚未开始正式执行,正如其注释所说:该集合的动画对象都是即将执行的动画对象
sDelayedAnims:该集合保存了设置了延迟执行方法的ObjectAnimator对象
sReadyAnims :如果sDelayedAnims里面的某一个延迟执行的动画对象已经到了执行的时间,那么该ObjectAnimation对象就会 从sDelayedAnims删除,并放入sReadyAnims;也即是说sReadyAnims里面的动画都话保存了延迟时间到期后可以开始执行的集合
sEndingAnims:当sAnimations里面的某个正在执行的ObjectAnimation执行完毕后,就把该对象从sAnimations移除并将其放入 sEndingAnims集合中
这几个集合之间数据的移动可以用如下图简单所示:
以上就是ObjectAnimator的基本流程,那么到现在各种条件都准备好了,是时候讨论下前面说的那些handleMessage是怎么回事儿了:
因为代码太长,所以先看看case ANIMATION_START分支都干了些神马:
//获取正在执行的动画集合
ArrayList<ValueAnimator> animations = sAnimations.get();
//获取延迟的动画集合
ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
switch (msg.what) {
case ANIMATION_START:
ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
if (animations.size() > 0 || delayedAnims.size() > 0) {
callAgain = false;
}
//如果还有要执行的动画
while (pendingAnimations.size() > 0) {
ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) pendingAnimations
.clone();
//清空pendingAnimations确保退出while循环
pendingAnimations.clear();
int count = pendingCopy.size();
//遍历pendingAnimations里面每一个ValueAnimator
for (int i = 0; i < count; ++i) {
ValueAnimator anim = pendingCopy.get(i);
if (anim.mStartDelay == 0) {
//主要是调用了initAnimation方法并将anim放入sAnimations里面的集合
//sAnimations保存了正在执行的动画对象
anim.startAnimation();
} else {
//还没开始执行
delayedAnims.add(anim);
}
}//end for
}//end while
//此处没有break
上面的代码也很简单就是将sPendingAnimations集合里面的数据分成两部分,正在执行的数据对象加入sAnimations集合中,没有执行的动画对象放入sDelayedAnims中;
继续分析case ANIMATION_FRAME的代码:
//获取延迟到期的动画集合
ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
//获取执行结束的动画集合
ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();
//把延迟处理的动画放入sReadyAnims集合中
int numDelayedAnims = delayedAnims.size();
for (int i = 0; i < numDelayedAnims; ++i) {
ValueAnimator anim = delayedAnims.get(i);
//如果延迟执行的动画可以执行了
if (anim.delayedAnimationFrame(currentTime)) {
//放入readyAnims准备执行
readyAnims.add(anim);
}
}//end for
//开始执行readyAnims集合里面的动画
int numReadyAnims = readyAnims.size();
if (numReadyAnims > 0) {
for (int i = 0; i < numReadyAnims; ++i) {
ValueAnimator anim = readyAnims.get(i);
//主要是调用了initAnimation方法并将anim放入sAnimations里面的集合
//sAnimations保存了正在执行的动画对象
anim.startAnimation();
anim.mRunning = true;
//从延迟队列里面删除对应的记录
delayedAnims.remove(anim);
}//end for
//清空准备好的序列
readyAnims.clear();
}
//变量正在执行的序列
int numAnims = animations.size();
int i = 0;
while (i < numAnims) {
ValueAnimator anim = animations.get(i);
//如果当前动画已经执行完毕
if (anim.animationFrame(currentTime)) {
//把执行完的对象放入endingAnims对象中
endingAnims.add(anim);
}
if (animations.size() == numAnims) {
++i;
} else {
--numAnims;
endingAnims.remove(anim);
}
}//end while
//省略部分代码
//如果仍然有尚未执行完的动画,继续发送ANIMATION_FRAME消息继续执行
if (callAgain
&& (!animations.isEmpty() || !delayedAnims.isEmpty())) {
sendEmptyMessageDelayed(
ANIMATION_FRAME,
Math.max(
0,
sFrameDelay
- (AnimationUtils
.currentAnimationTimeMillis() - currentTime)));
}
break;
上面的代码也很简单,结合上面的流程图不难发现也就是ObjectAnimation对象在这些集合里面转移的过程,就不在赘述;但是该case语句分支有两个核心点:
1)怎么判断当前ObjectAnimation已经结束呢?
2)如果还有正在执行的动画或者延迟动画集合非空,继续发送延迟消息ANIMATION_FRAME。
其实判断ObjectAnimation是否已经结束的方法前面已经说过,就是animationFrame方法,因为会定时发送ANIMATION_FRAME消息,animationFrame方法也会得到执行,如此往复这样一个ObjectAnimation对象的完整流程就可以完成了。
在animationFrame里面有如下代码来判断一个ObjectAnimation动画是否完成:
1)如果fraction>=1切mRepeatCount已经达到了指定次数。代码如下
if (fraction >= 1f) {
if (mCurrentIteration < mRepeatCount
|| mRepeatCount == INFINITE) {
// Time to repeat
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
if (mRepeatMode == REVERSE) {
mPlayingBackwards = mPlayingBackwards ? false : true;
}
mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
} else {
done = true;//标明动画已经结束
fraction = Math.min(fraction, 1.0f);
}
}
到此为止,属性动画的执行流程已经描述完毕,篇幅较长,如果有不当的地方欢迎批评指正,共同学习和进步。
以上是关于属性动画简单分析的主要内容,如果未能解决你的问题,请参考以下文章
Android属性动画ValueAnimator源码简单分析
自己定义View时,用到Paint Canvas的一些温故,简单的帧动画(动画一 ,"掏粪男孩Gif"顺便再提提onWindowFocusChanged)(代码片段