Android在3.0版本中引入了新的动画实现:属性动画。我们一般称之为
Animator
。这种动画通过变更控件属性达到动画效果。其中,属性动画最重要的一点,就是控制了动画的时序,我们不妨来看下属性动画的简单用法:
//code1
ValueAnimator animator = ValueAnimator.ofInt(0,100)//line1
.setDuration(100);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Log.d("animator-demo","onAnimationUpdate "+valueAnimator.getAnimatedValue());
}
});
animator.start();
code1
非常简单,就是定义了这样的ValueAnimator
对象:
1.对象的过渡区间设置为0~100
2.设置了动画时间100
3.设置了一个AnimatorUpdateListener
监听器
我们通过打印animator-demo
日志可以得到:
11-08 01:21:00.594 29311 29311 D animator-demo: onAnimationUpdate 0
11-08 01:21:00.606 29311 29311 D animator-demo: onAnimationUpdate 0
11-08 01:21:00.626 29311 29311 D animator-demo: onAnimationUpdate 7
11-08 01:21:00.644 29311 29311 D animator-demo: onAnimationUpdate 30
11-08 01:21:00.664 29311 29311 D animator-demo: onAnimationUpdate 59
11-08 01:21:00.681 29311 29311 D animator-demo: onAnimationUpdate 84
11-08 01:21:00.701 29311 29311 D animator-demo: onAnimationUpdate 98
11-08 01:21:00.718 29311 29311 D animator-demo:
onAnimationUpdate 100
可以看出,通过
ValueAnimator
我们很平滑的从0过渡到了100,而并不关心其中的时序和数值的对应关系。ValueAnimator
是整个属性动画的基础,因此本章将重点分析ValueAnimator
的内部机制。我们先来看下ValueAnimator
的继承关系图:
ValueAnimator
继承于Animator
,在Animator
中只是定义了一些常用接口和一些基础api,比如:
1.AnimatorListener回调
2.start,end 函数等
此外,ValueAnimator
还实现了接口AnimationFrameCallback
。这个接口是在AnimationHandler
类中定义的回调接口,这个接口,我们一会儿会提到,它跟我们的动画息息相关。
有了以上的知识储备,我们可以开始我们下一步,我们要使用
ValueAnimator
,就需要构造它,上面的code1例子中我们使用了ValueAnimator.ofInt(0,100)
的静态工厂方式去构造一个int属性集合的ValueAnimator
对象。一般情况下,你也可以通过new ValueAnimator()
的方式构建一个ValueAnimator
对象,然后通过set[Type]Values
方式注入你所需要的值集合。但是相比第一种方法,通过new
方式的构造手段代码偏多而且不集中,也不利于维护(当然凡事没有绝对,需要考虑你自己的业务场景)。
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
在
ofInt
函数中,ValueAnimator
将生成一个ValueAnimator
对象,然后通过调用setIntValues
方法将values
数组转为PropertyValuesHolder
对象。而int
数组类型values
往PropertyValuesHolder
对象的转换是通过PropertyValuesHolder
的ofInt
方法实现:
//code PropertyValuesHolder.java
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntPropertyValuesHolder(propertyName, values);
}
PropertyValuesHolder
是什么呢?我们可以通过PropertyValuesHolder
的注释看出一些端倪:
/**
* This class holds information about a property and the values that that property
* should take on during an animation. PropertyValuesHolder objects can be used to create
* animations with ValueAnimator or ObjectAnimator that operate on several different properties
* in parallel.
*/
大致意思就是一个存放属性和值的容器,而每次动画的过程中都会从这个容器中取值或者设置值。
PropertyValuesHolder
的ofInt
方法将返回一个IntPropertyValuesHolder
类型对象。这个类型的作用就像它名字一样限定了Holder
中所存放的类型是Int
类型。Ok,我们说到这里,我们可以看到一个简单ValueAnimator
对象的生成,实际上伴随着多个类型的对象。
ValueAnimator
的setDuration
方法纯粹就是记录一个mDuration
时间,没有特别的操作。
@Override
public ValueAnimator setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
mDuration = duration;
return this;
}
我们重点看下
start()
方法:
//code ValueAnimator.java
@Override
public void start() {
start(false);
}
private void start(boolean playBackwards/*是否有返回操作*/) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
....
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = 0;
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
//加入到animationHandler管理
if (mStartDelay == 0 || mSeekFraction >= 0) {
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
在
ValueAnimator
的start
函数中会调用内部的start(boolean playBackwards)
函数。参数playBackwards
代表动画是否还有回弹操作。可以通过ValueAnimator.reverse()
方法将其设置为true
。
我们刚才说到,
ValueAnimator
实现了AnimationFrameCallback
接口,ValueAnimator.start(boolean)
代码中,ValueAnimator
先通过一个AnimationHandler.getInstance()
方法获取线程内的AnimationHandler
单例,然后将实现了AnimationFrameCallback
的ValueAnimator
对象(也就是自己)通过addAnimationFrameCallback
方法加入到AnimationHandler
对象中去。
//AnimationHandler.java
//sAnimatorHandler是一个ThreadLocal变量,用于存储AnimationHandler的线程单例
public static AnimationHandler getInstance() {
if (sAnimatorHandler.get() == null) {
sAnimatorHandler.set(new AnimationHandler());
}
return sAnimatorHandler.get();
}
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);//加入队列
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
在
AnimationHandler
类的addAnimationFrameCallback
方法中,AnimationHandler
将会往getProvider()
对象中post一个回调对象mFrameCallback
,而所有的绘制都将通过这个对象进行队列遍历来实现。
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);//进行循环动画
}
}
};
mFrameCallback
是Choreographer.FrameCallback
的实现类,它实现了doFrame
回调接口,在这个接口中,将通过调用doAnimationFrame
函数来执行mAnimationCallbacks
队列中的动画,最后当mAnimationCallbacks
队列中还有对象的时候,将再次执行getProvider().postFrameCallback(this);
函数进行循环动画操作。
getProvider()
返回一个AnimationFrameCallbackProvider
类型的对象。AnimationFrameCallbackProvider
是什么呢?AnimationFrameCallbackProvider
其实就是一个跟最终的动画操作对象Choreographer
交互的一个接口对象。而在AnimationHandler
类中,它的实现类是MyFrameCallbackProvider
。
//code AnimationHandler.java
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
MyFrameCallbackProvider
内部实现了跟Choreographer
对象的操作:
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
....
}
Choreographer
对象是什么呢?如果你做过动画,或者深入研究过动画相关,相信对这个类或者这个对象并不陌生,它是android系统中所有动画和绘制的管理者。简单概括起来,Choreographer
就是一个步调管理者,它是什么步调呢?就是以16ms左右为频率做组成的一个绘制信号。这部分涉及到Android的绘制系统,我们不深入探究,我们可以写个代码简单了解一下:
long time = SystemClock.uptimeMillis();
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long l) {
Log.d("Choreographer","doFrame use time= "+(SystemClock.uptimeMillis() - time)+"ms");
time = SystemClock.uptimeMillis();
Choreographer.getInstance().postFrameCallback(this);
}
});
我们往
Choreographer
对象中post
一个FrameCallback
匿名对象,通过变量time来计算每次步调的时差。最后我们可以在日志中输出:
11-08 05:31:25.290 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.308 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.327 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.345 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.363 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.383 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.400 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.421 9080 9080 D Choreographer: doFrame use time= 21ms
11-08 05:31:25.439 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.457 9080 9080 D Choreographer: doFrame use time= 17ms
11-08 05:31:25.474 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.494 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.513 9080 9080 D Choreographer: doFrame use time= 19ms
可以看出,
Choreographer
会在一定频率的步调中执行绘制函数。而这种步调或者是用软件模拟,或者是通过系统的 VSYNC 信号实现。我们来整理一下ValueAnimator
的start
流程:
通过上面的流程图我们可以看出,在
AnimationHandler
进行绘制的时候,实际上是调用了ValueAnimator
的doAnimationFrame
方法:
public final void doAnimationFrame(long frameTime) {
AnimationHandler handler = AnimationHandler.getInstance();
if (mLastFrameTime == 0) {
// First frame
handler.addOneShotCommitCallback(this);
//对于第一帧添加到commit回调
if (mStartDelay > 0) {
startAnimation();
}
if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; }
mLastFrameTime = frameTime;
if (mPaused) {
mPauseTime = frameTime;
handler.removeCallback(this);
return;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mStartTime += (frameTime - mPauseTime);
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
handler.addOneShotCommitCallback(this);
}
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);//动画处理函数
if (finished) {
endAnimation();
}
}
对于第一帧或者
resume
后的动画,将通过handler
对象的addOneShotCommitCallback
方法将Callback
对象加入到Commit
队列中去。之后将调用动画处理函数:animateBasedOnTime(long)
。
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
....
animateValue(currentIterationFraction);
}
return done;
}
animateBasedOnTime
函数将调用animateValue
函数实现真正意义上的属性赋值。
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;//归一化后的进度参数
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);//计算值
}
if (mUpdateListeners != null) {//通知回调
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
由于动画的整个过程相当于是一个以时间为变量的函数:
x = f(t)
。(t代表时间)
为了方便计算,动画的计算过程会先将时间变量归一化,行程进度变量fraction
,然后通过差值计算得到相应的差值变量赋值给fraction
。
比如:你执行动画400ms
,现在你执行到了200ms
,那么你归一化以后的进度变量就为200/400 = .5f
。如果你采用的是线性差值器的话那么你的差值变量也同样为.5f
。
mValues
变量指的就是我们上面提到的PropertyValuesHolder
变量。PropertyValuesHolder
的calculateValue
函数,将调用mKeyframeSet
的getValue
函数,而这个函数的参数,就是我们上面
//PropertyValuesHolder.java
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
KeyframeSet是什么呢?我们通过阅读这个成员的注释可以看出一些门道:
/**
* The set of keyframes (time/value pairs) that define this animation.
*/
KeyframeSet mKeyframeSet = null;
简要说明,就是存储了一些value值,什么样的value值呢?用于计算时间和对应值vaue的value集合。我们不妨看下mKeyframeSet是在哪儿被赋值的。由于我们是通过"ValueAnimator.ofInt()"方式来生成一个
ValueAnimator
对象,因此,ValueAnimator
将会通过setIntValues
函数给属性赋值:
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframeSet = KeyframeSet.ofInt(values);
//静态构造KeyframeSet
}
public static KeyframeSet ofInt(int... values//传入的是[0,100]) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else { //step2
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
由于我们此时传入的数组是
[0,100]
,所以if
语句将跳转到我们的step2
处,之后给每一个值都生成一个Keyframe
对象帧放入keyframes
数组集合中,再将数组集合keyframes
存入对象IntKeyframeSet
中。Keyframe
通过静态方法ofInt
来构建一个Keyframe
对象,这个对象第一个浮点参数,代表你这个值在数组中的偏移。比如你的数组是[0,1,2,3,4]
,那么2在此数组中的偏移为2 / (5 -1) = 50%
。我们再回到PropertyValuesHolder
的calculateValue
方法,此方法里调用了KeyFrameSet
的getValue
方法:
//KeyFrameSet.getValue
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
KeyFrameSet
的getValue
方法,会通过传入的差值变量,匹配到 最接近且不超过 的一个时间帧,并通过mEvaluator
计算器计算返回给上层调用。我们再回朔一下ValueAnimator
的绘制过程:
-
ValueAnimator
调用start方法将自己放入到AnimatorHandler
的队列中去,AnimatorHandler
将post
一个FrameCallback
到Choreographer
的动画消息处理队列中去。
2.当收到一条VSYNC
消息或者是绘制指令,将回调ValueAnimator
的doAnimationFrame
方法,而doAnimationFrame()
方法中会调用animateBasedOnTime()
-> animateValue()
方法用于计算。
-
animateValue()
方法计算中会调用PropertyValuesHolder[] mValues
的calculateValue
方法用于计算当前时刻的差值:
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
并将当前值保存在mAnimatedValue
变量中去。
我们通过上面的流程解释了
Animator
动画过程中的差值计算,那么接下去,我们就需要把这个值注入到我们的控件属性中去,这样才能够实现动画的效果。那么我们计算好了属性值,我们需要在哪儿注入到我们的控件对象中去呢?而且,属性可能对应的是不同的类型,我们又如何区分不同的类型呢?
我们现在解答第一个问题:
我们看下一下这个例子:
View view = ...;
view.animate().translationX(500).start();
这时候,我们会看见我们的控件
view
沿着x轴方向正方向平移500个单位
。实际上,这种动画的实现就是用的我们上面的属性动画,而属性动画的计算过程跟我们上述的一摸一样。那么它又是如何将计算好的结果设置到View
对象上的呢?
首先,View.animate()
方法返回的是一个ViewPropertyAnimator
,不要被它的名字所误导,它并不是一个Animator
,它的作用其实类似一个Animator
的Builder
对象
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
当
ViewPropertyAnimator
调用start
和startAnimation
方法的时候,ViewPropertyAnimator
会真正的构造我们的属性动画ValueAnimator
。
//code ViewPropertyAnimator.java
public void start() {
...
startAnimation();
}
private void startAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
....
animator.addUpdateListener(mAnimatorEventListener);//增加回调
animator.addListener(mAnimatorEventListener);//增加回调
...
animator.start();
}
这里,
ViewPropertyAnimator
会给生成的ValueAnimator
对象增加非常重要的接口mAnimatorEventListener
。我们知道,ValueAnimator
在计算完每一帧以后,都会回调AnimatorUpdateListener
接口的onAnimationUpdate
方法:
//code ValueAnimator.java
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);//回调接口
}
}
}
也就是说,每次计算完后,ValueAnimator都会回调mAnimatorEventListener对象的onAnimationUpdate方法,而在mAnimatorEventListener对象的实现中,将会把计算好的值赋予View对象:
//code AnimatorEventListener.java
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PropertyBundle propertyBundle = mAnimatorMap.get(animation);
if (propertyBundle == null) {
// Shouldn't happen, but just to play it safe
return;
}
boolean hardwareAccelerated = mView.isHardwareAccelerated();
boolean alphaHandled = false;
if (!hardwareAccelerated) {
mView.invalidateParentCaches();
}
float fraction = animation.getAnimatedFraction();
int propertyMask = propertyBundle.mPropertyMask;
if ((propertyMask & TRANSFORM_MASK) != 0) {
mView.invalidateViewProperty(hardwareAccelerated, false);
}
ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
if (valueList != null) {
int count = valueList.size();
for (int i = 0; i < count; ++i) {
NameValuesHolder values = valueList.get(i);
float value = values.mFromValue + fraction * values.mDeltaValue;
if (values.mNameConstant == ALPHA) {
alphaHandled = mView.setAlphaNoInvalidation(value);
} else {
setValue(values.mNameConstant, value);//设置值
}
}
}
....
}
这里主要调用了个叫
setValue(values.mNameConstant, value);
的方法,而此方法会通过传入的名字常量执行不同的操作:
private void setValue(int propertyConstant, float value) {
final View.TransformationInfo info = mView.mTransformationInfo;
final RenderNode renderNode = mView.mRenderNode;
switch (propertyConstant) {
case TRANSLATION_X:
renderNode.setTranslationX(value);
break;
case TRANSLATION_Y:
renderNode.setTranslationY(value);
break;
case TRANSLATION_Z:
renderNode.setTranslationZ(value);
break;
case ROTATION:
renderNode.setRotation(value);
break;
case ROTATION_X:
renderNode.setRotationX(value);
break;
case ROTATION_Y:
renderNode.setRotationY(value);
break;
....
}
}