Android在3.0版本中引入了新的動畫實現(xiàn):屬性動畫。我們一般稱之為
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.對象的過渡區(qū)間設(shè)置為0~100
2.設(shè)置了動畫時間100
3.設(shè)置了一個AnimatorUpdateListener
監(jiān)聽器
我們通過打印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,而并不關(guān)心其中的時序和數(shù)值的對應關(guān)系宝穗。ValueAnimator
是整個屬性動畫的基礎(chǔ),因此本章將重點分析ValueAnimator
的內(nèi)部機制哄辣。我們先來看下ValueAnimator
的繼承關(guān)系圖:
ValueAnimator
繼承于Animator
,在Animator
中只是定義了一些常用接口和一些基礎(chǔ)api,比如:
1.AnimatorListener回調(diào)
2.start,end 函數(shù)等
此外,ValueAnimator
還實現(xiàn)了接口AnimationFrameCallback
蹄衷。這個接口是在AnimationHandler
類中定義的回調(diào)接口,這個接口,我們一會兒會提到,它跟我們的動畫息息相關(guān)说铃。
有了以上的知識儲備,我們可以開始我們下一步,我們要使用
ValueAnimator
,就需要構(gòu)造它,上面的code1例子中我們使用了ValueAnimator.ofInt(0,100)
的靜態(tài)工廠方式去構(gòu)造一個int屬性集合的ValueAnimator
對象访惜。一般情況下,你也可以通過new ValueAnimator()
的方式構(gòu)建一個ValueAnimator
對象,然后通過set[Type]Values
方式注入你所需要的值集合嘹履。但是相比第一種方法,通過new
方式的構(gòu)造手段代碼偏多而且不集中,也不利于維護(當然凡事沒有絕對,需要考慮你自己的業(yè)務場景)腻扇。
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
函數(shù)中,ValueAnimator
將生成一個ValueAnimator
對象,然后通過調(diào)用setIntValues
方法將values
數(shù)組轉(zhuǎn)為PropertyValuesHolder
對象。而int
數(shù)組類型values
往PropertyValuesHolder
對象的轉(zhuǎn)換是通過PropertyValuesHolder
的ofInt
方法實現(xiàn):
//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.
*/
大致意思就是一個存放屬性和值的容器,而每次動畫的過程中都會從這個容器中取值或者設(shè)置值幼苛。
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
函數(shù)中會調(diào)用內(nèi)部的start(boolean playBackwards)
函數(shù)配并。參數(shù)playBackwards
代表動畫是否還有回彈操作括荡。可以通過ValueAnimator.reverse()
方法將其設(shè)置為true
溉旋。
我們剛才說到,
ValueAnimator
實現(xiàn)了AnimationFrameCallback
接口,ValueAnimator.start(boolean)
代碼中,ValueAnimator
先通過一個AnimationHandler.getInstance()
方法獲取線程內(nèi)的AnimationHandler
單例,然后將實現(xiàn)了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一個回調(diào)對象mFrameCallback
,而所有的繪制都將通過這個對象進行隊列遍歷來實現(xiàn)。
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);//進行循環(huán)動畫
}
}
};
mFrameCallback
是Choreographer.FrameCallback
的實現(xiàn)類,它實現(xiàn)了doFrame
回調(diào)接口,在這個接口中,將通過調(diào)用doAnimationFrame
函數(shù)來執(zhí)行mAnimationCallbacks
隊列中的動畫,最后當mAnimationCallbacks
隊列中還有對象的時候,將再次執(zhí)行getProvider().postFrameCallback(this);
函數(shù)進行循環(huán)動畫操作观腊。
getProvider()
返回一個AnimationFrameCallbackProvider
類型的對象邑闲。AnimationFrameCallbackProvider
是什么呢?AnimationFrameCallbackProvider
其實就是一個跟最終的動畫操作對象Choreographer
交互的一個接口對象梧油。而在AnimationHandler
類中,它的實現(xiàn)類是MyFrameCallbackProvider
苫耸。
//code AnimationHandler.java
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
MyFrameCallbackProvider
內(nèi)部實現(xiàn)了跟Choreographer
對象的操作:
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
....
}
Choreographer
對象是什么呢?如果你做過動畫,或者深入研究過動畫相關(guān),相信對這個類或者這個對象并不陌生,它是android系統(tǒng)中所有動畫和繪制的管理者儡陨。簡單概括起來,Choreographer
就是一個步調(diào)管理者,它是什么步調(diào)呢褪子?就是以16ms左右為頻率做組成的一個繪制信號量淌。這部分涉及到Android的繪制系統(tǒng),我們不深入探究,我們可以寫個代碼簡單了解一下:
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來計算每次步調(diào)的時差。最后我們可以在日志中輸出:
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
會在一定頻率的步調(diào)中執(zhí)行繪制函數(shù)嫌褪。而這種步調(diào)或者是用軟件模擬,或者是通過系統(tǒng)的 VSYNC 信號實現(xiàn)类少。我們來整理一下ValueAnimator
的start
流程:
通過上面的流程圖我們可以看出,在
AnimationHandler
進行繪制的時候,實際上是調(diào)用了ValueAnimator
的doAnimationFrame
方法:
public final void doAnimationFrame(long frameTime) {
AnimationHandler handler = AnimationHandler.getInstance();
if (mLastFrameTime == 0) {
// First frame
handler.addOneShotCommitCallback(this);
//對于第一幀添加到commit回調(diào)
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);//動畫處理函數(shù)
if (finished) {
endAnimation();
}
}
對于第一幀或者
resume
后的動畫,將通過handler
對象的addOneShotCommitCallback
方法將Callback
對象加入到Commit
隊列中去。之后將調(diào)用動畫處理函數(shù):animateBasedOnTime(long)
渔扎。
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
....
animateValue(currentIterationFraction);
}
return done;
}
animateBasedOnTime
函數(shù)將調(diào)用animateValue
函數(shù)實現(xiàn)真正意義上的屬性賦值硫狞。
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;//歸一化后的進度參數(shù)
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);//計算值
}
if (mUpdateListeners != null) {//通知回調(diào)
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
由于動畫的整個過程相當于是一個以時間為變量的函數(shù):
x = f(t)
。(t代表時間)
為了方便計算,動畫的計算過程會先將時間變量歸一化,行程進度變量fraction
,然后通過差值計算得到相應的差值變量賦值給fraction
晃痴。
比如:你執(zhí)行動畫400ms
,現(xiàn)在你執(zhí)行到了200ms
,那么你歸一化以后的進度變量就為200/400 = .5f
残吩。如果你采用的是線性差值器的話那么你的差值變量也同樣為.5f
。
mValues
變量指的就是我們上面提到的PropertyValuesHolder
變量倘核。PropertyValuesHolder
的calculateValue
函數(shù),將調(diào)用mKeyframeSet
的getValue
函數(shù),而這個函數(shù)的參數(shù),就是我們上面
//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
函數(shù)給屬性賦值:
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframeSet = KeyframeSet.ofInt(values);
//靜態(tài)構(gòu)造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);
}
由于我們此時傳入的數(shù)組是
[0,100]
,所以if
語句將跳轉(zhuǎn)到我們的step2
處,之后給每一個值都生成一個Keyframe
對象幀放入keyframes
數(shù)組集合中,再將數(shù)組集合keyframes
存入對象IntKeyframeSet
中漏益。Keyframe
通過靜態(tài)方法ofInt
來構(gòu)建一個Keyframe
對象,這個對象第一個浮點參數(shù),代表你這個值在數(shù)組中的偏移蛹锰。比如你的數(shù)組是[0,1,2,3,4]
,那么2在此數(shù)組中的偏移為2 / (5 -1) = 50%
。我們再回到PropertyValuesHolder
的calculateValue
方法,此方法里調(diào)用了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
計算器計算返回給上層調(diào)用铜犬。我們再回朔一下ValueAnimator
的繪制過程:
-
ValueAnimator
調(diào)用start方法將自己放入到AnimatorHandler
的隊列中去,AnimatorHandler
將post
一個FrameCallback
到Choreographer
的動畫消息處理隊列中去。
2.當收到一條VSYNC
消息或者是繪制指令,將回調(diào)ValueAnimator
的doAnimationFrame
方法,而doAnimationFrame()
方法中會調(diào)用animateBasedOnTime()
-> animateValue()
方法用于計算轻庆。
-
animateValue()
方法計算中會調(diào)用PropertyValuesHolder[] mValues
的calculateValue
方法用于計算當前時刻的差值:
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
并將當前值保存在mAnimatedValue
變量中去癣猾。
我們通過上面的流程解釋了
Animator
動畫過程中的差值計算,那么接下去,我們就需要把這個值注入到我們的控件屬性中去,這樣才能夠?qū)崿F(xiàn)動畫的效果。那么我們計算好了屬性值,我們需要在哪兒注入到我們的控件對象中去呢余爆?而且,屬性可能對應的是不同的類型,我們又如何區(qū)分不同的類型呢纷宇?
我們現(xiàn)在解答第一個問題:
我們看下一下這個例子:
View view = ...;
view.animate().translationX(500).start();
這時候,我們會看見我們的控件
view
沿著x軸方向正方向平移500個單位
。實際上,這種動畫的實現(xiàn)就是用的我們上面的屬性動畫,而屬性動畫的計算過程跟我們上述的一摸一樣蛾方。那么它又是如何將計算好的結(jié)果設(shè)置到View
對象上的呢像捶?
首先,View.animate()
方法返回的是一個ViewPropertyAnimator
,不要被它的名字所誤導,它并不是一個Animator
,它的作用其實類似一個Animator
的Builder
對象
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
當
ViewPropertyAnimator
調(diào)用start
和startAnimation
方法的時候,ViewPropertyAnimator
會真正的構(gòu)造我們的屬性動畫ValueAnimator
。
//code ViewPropertyAnimator.java
public void start() {
...
startAnimation();
}
private void startAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
....
animator.addUpdateListener(mAnimatorEventListener);//增加回調(diào)
animator.addListener(mAnimatorEventListener);//增加回調(diào)
...
animator.start();
}
這里,
ViewPropertyAnimator
會給生成的ValueAnimator
對象增加非常重要的接口mAnimatorEventListener
转捕。我們知道,ValueAnimator
在計算完每一幀以后,都會回調(diào)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);//回調(diào)接口
}
}
}
也就是說,每次計算完后,ValueAnimator都會回調(diào)mAnimatorEventListener對象的onAnimationUpdate方法,而在mAnimatorEventListener對象的實現(xiàn)中,將會把計算好的值賦予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);//設(shè)置值
}
}
}
....
}
這里主要調(diào)用了個叫
setValue(values.mNameConstant, value);
的方法,而此方法會通過傳入的名字常量執(zhí)行不同的操作:
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;
....
}
}