本文假定你已经对属性动画有了一定的了解,至少使用过属性动画。下面我们就从属性动画最简单的使用开始。
ObjectAnimator
.ofInt(target,propName,values[])
.setInterpolator(LinearInterpolator)
.setEvaluator(IntEvaluator)
.setDuration(500)
.start();
相信这段代码对你一定不陌生,代码中有几个地方是本文中将要重点关注的,setInterpolator(...)、setEvaluator(...)、setDuration(...)在源代码中是如何被使用的。另外,我们也将重点关注Android中属性动画是如何一步步地实现动画效果的(精确到每一帧(frame))。最后啰嗦几句,本文中使用的代码是Android 4.2.2。
上面代码的作用就是生成一个属性动画,根据ofInt()我们知道只是一个属性值类型为Int的View的动画。先放过其他的函数,从ObjectAnimator的start()函数开始。
public void start() {
//...省略不必要代码
super.start();
}
从代码中我们知道,它调用了父类的start()方法,也就是ValueAnimator.start()。这个方法内部又调用了自身类内部的start(boolean playBackwards)方法。
/**
* 方法简要介绍:
* 这个方法开始播放动画。这个start()方法使用一个boolean值playBackwards来判断是否需
* 要回放动画。这个值通常为false,但当它从reverse()方法被调用的时候也
* 可能为true。有一点需要注意的是,这个方法必须从UI主线程调用。
*/
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mPlayingBackwards = playBackwards;
mCurrentIteration = 0;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
AnimationHandler animationHandler = getOrCreateAnimationHandler();
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
// 在动画实际运行前,设置动画的初始值
setCurrentPlayTime(0);
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
}
animationHandler.start();
}
对代码中几个值的解释:
- mPlayingStated代表当前动画的状态。用于找出什么时候开始动画(if state == STOPPED)。当然也用于在animator被调用了cancel()或 end()在动画的最后一帧停止它。可能的值为STOPPED, RUNNING, SEEKED.
- mStarted是Animator中一个额外用于标识播放状态的值,用来指示这个动画是否需要延时执行。
- mStartedDelay指示这个动画是否已经从startDelay中开始执行。
- AnimationHandler animationHandler 是一个实现了Runnable接口的ValueAnimator内部类,暂时先放过,后面我们会具体谈到。
从上面这段代码中,我们了解到一个ValueAnimator有它自己的状态(STOPPED, RUNNING, SEEKED),另外是否延时也影响ValueAnimator的执行。代码的最后调用了animationHandler.start(),看来动画就是从这里启动的。别急,我们还没初始化ValueAnimator呢,跟进setCurrentPlayTime(0)看看。
public void setCurrentPlayTime(long playTime) {
initAnimation();
long currentTime = AnimationUtils.currentAnimationTimeMillis();
if (mPlayingState != RUNNING) {
mSeekTime = playTime;
mPlayingState = SEEKED;
}
mStartTime = currentTime - playTime;
doAnimationFrame(currentTime);
}
这个函数在animation开始前,设置它的初始值。这个函数用于设置animation进度为指定时间点。playTime应该介于0到animation的总时间之间,包括animation重复执行的时候。如果animation还没有开始,那么它会等到被设置这个时间后才开始。如果animation已经运行,那么setCurrentTime()会将当前的进度设置为这个值,然后从这个点继续播放。
接下来让我们看看initAnimation()
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
这个函数一看就觉得跟初始化动画有关。这个函数在处理动画的第一帧前就会被调用。如果startDelay不为0,这个函数就会在就会在延时结束后调用。它完成animation最终的初始化。
那么mValues是什么呢?还记得我们在文章的开头介绍ObjectAnimator的使用吧?还有一个ofInt(T target, Property property, int... values)方法没有介绍。官方文档中对这个方法的解释是:构造并返回一个在int类型的values数值之间ObjectAnimator对象。当values只有一个值的时候,这个值就作为animator的终点值。如果有两个值的话,那么这两个值就作为开始值和结束值。如果有超过两个以上的值的话,那么这些值就作为开始值,作为animator运行的中间值,以及结束值。这些值将均匀地分配到animator的持续时间。
先中断ObjectAnimator.start()流程的分析,回到开头ObjectAnimator.ofInt(...)
接下来让我们深入ofInt(...)的内部看看。