本文假定你已經對屬性動畫有了必定的瞭解,至少使用過屬性動畫。下面咱們就從屬性動畫最簡單的使用開始。html
ObjectAnimator .ofInt(target,propName,values[]) .setInterpolator(LinearInterpolator) .setEvaluator(IntEvaluator) .setDuration(500) .start();
相信這段代碼對你必定不陌生,代碼中有幾個地方是本文中將要重點關注的,setInterpolator(...)、setEvaluator(...)、setDuration(...)在源代碼中是如何被使用的。另外,咱們也將重點關注Android中屬性動畫是如何一步步地實現動畫效果的(精確到每一幀(frame))。最後囉嗦幾句,本文中使用的代碼是Android 4.2.2。java
上面代碼的做用就是生成一個屬性動畫,根據ofInt()咱們知道只是一個屬性值類型爲Int的View的動畫。先放過其餘的函數,從ObjectAnimator的start()函數開始。android
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(); }
對代碼中幾個值的解釋:數據結構
從上面這段代碼中,咱們瞭解到一個ValueAnimator有它本身的狀態(STOPPED, RUNNING, SEEKED),另外是否延時也影響ValueAnimator的執行。代碼的最後調用了animationHandler.start(),看來動畫就是從這裏啓動的。別急,咱們還沒初始化ValueAnimator呢,跟進setCurrentPlayTime(0)看看。app
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()會將當前的進度設置爲這個值,而後從這個點繼續播放。ide
接下來讓咱們看看initAnimation()函數
void initAnimation() { if (!mInitialized) { int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].init(); } mInitialized = true; } }
這個函數一看就以爲跟初始化動畫有關。這個函數在處理動畫的第一幀前就會被調用。若是startDelay不爲0,這個函數就會在就會在延時結束後調用。它完成animation最終的初始化。oop
那麼mValues是什麼呢?還記得咱們在文章的開頭介紹ObjectAnimator的使用吧?還有一個ofInt(T target, Property<T, Integer> property, int... values)方法沒有介紹。官方文檔中對這個方法的解釋是:構造並返回一個在int類型的values數值之間ObjectAnimator對象。當values只有一個值的時候,這個值就做爲animator的終點值。若是有兩個值的話,那麼這兩個值就做爲開始值和結束值。若是有超過兩個以上的值的話,那麼這些值就做爲開始值,做爲animator運行的中間值,以及結束值。這些值將均勻地分配到animator的持續時間。post
接下來讓咱們深刻ofInt(...)的內部看看。
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setIntValues(values); return anim; }
對這個函數的解釋:
再看看anim.setIntValues這個函數
public void setIntValues(int... values) { if (mValues == null || mValues.length == 0) { // No values yet - this animator is being constructed piecemeal. Init the values with // whatever the current propertyName is if (mProperty != null) { setValues(PropertyValuesHolder.ofInt(mProperty, values)); } else { setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); } } else { super.setIntValues(values); } }
一開始的時候,mProperty確定尚未初始化,咱們進去setValues(PropertyValuesHolder.ofInt(mPropertyName, values))看看。這裏涉及到PropertyValuesHolder這個類。PropertyValuesHolder這個類擁有關於屬性的信息和動畫期間須要使用的值。PropertyValuesHolder對象能夠用來和ObjectAnimator或ValueAnimator一塊兒建立能夠並行操做不PropertyValuesHolder同屬性的animator。
那麼PropertyValuesHolder.ofInt()是幹嗎用的呢?它經過傳入的屬性和values來構造並返回一個特定類型的PropertyValuesHolder對象(在這裏是IntPropertyValuesHolder類型)。
public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); }
在IntPropertyValuesHolder內部
public IntPropertyValuesHolder(String propertyName, int... values) { super(propertyName); setIntValues(values); } @Override public void setIntValues(int... values) { super.setIntValues(values); mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; }
跳轉到父類(PropertyValuesHolder)的setIntValues
public void setIntValues(int... values) { mValueType = int.class; mKeyframeSet = KeyframeSet.ofInt(values); }
這個函數其實跟咱們前面介紹到的 PropertyValueHolder的構造函數類似,它就是設置動畫過程當中須要的值。若是隻有一個值,那麼這個值就假定爲animator的終點值,動畫的初始值會自動被推斷出來,經過對象的getter方法獲得。固然,若是全部值都爲空,那麼一樣的這些值也會在動畫開始的時候也會自動被填上。這套自動推斷填值的機制只在PropertyValuesHolder對象跟ObjectAnimator一塊兒使用的時候纔有效,而且有一個能從propertyName自動推斷出的getter方法這些條件都成立的時候才能用,否則PropertyValuesHolder沒有辦法決定這些值是什麼。
接下來咱們看到KeyframeSet.ofInt(values)方法。KeyframeSet這個類持有Keyframe的集合,在一組給定的animator的關鍵幀(keyframe)中會被ValueAnimator用來計算值。這個類的訪問權限爲包可見,由於這個類實現Keyframe怎麼被存儲和使用的具體細節,外部不須要知道。
接下來咱們看看KeyframeSet.ofInt(values)方法。
public static KeyframeSet ofInt(int... values) { 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 { 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); }
在這個方法裏面,咱們看見從最開始的ObjectAnimator.ofInt(target,propName,values[]),也就是咱們在文章的開頭使用系統提供的動畫初始化函數中傳入的int數組,在這裏獲得了具體的使用。先不關心具體的使用Keyframe.ofInt(...)。從這裏咱們就能夠知道原來Android SDK經過傳入的int[]的長度來決定animator中每一個幀(frame)的值。具體的對傳入的int[]的使用能夠參考文章裏面對ObjectAnimator.ofInt(...)的介紹。在KeyframeSet.ofInt這個函數的最後一句話使用了IntKeyframeSet的構造函數來初始化這些Keyframe。
public IntKeyframeSet(IntKeyframe... keyframes) { super(keyframes); }
在IntKeyframeSet的構造函數中又調用父類KeyframeSet的構造函數來實現。
public KeyframeSet(Keyframe... keyframes) { mNumKeyframes = keyframes.length; mKeyframes = new ArrayList<Keyframe>(); mKeyframes.addAll(Arrays.asList(keyframes)); mFirstKeyframe = mKeyframes.get(0); mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); mInterpolator = mLastKeyframe.getInterpolator(); }
從這個構造函數中咱們又能夠了解到剛剛初始化後的Keyframe數組的第一項和最後一項(也就是第一幀和最後一幀)獲得了優先的待遇,做爲在KeyframeSet中的字段,估計是爲了後面計算動畫開始和結束的時候方便。
小結ObjectValue、PropertyValueHolder、KeyframeSet的關係
咱們繞了好久,不知道是否把你弄暈了,這裏稍稍總結一下。咱們就不從調用的順序一步步分析下來了,太長了。我直接說說ObjectValue、PropertyValueHolder、KeyframeSet之間的關係。這三個類比較有特色的地方,ObjectAnimator無疑是對的API接口,ObjectAnimator持有PropertyValuesHolder做爲存儲關於將要進行動畫的具體對象(一般是View類型的控件)的屬性和動畫期間須要的值。而PropertyValueHolder又使用KeyframeSet來保存animator從開始到結束期間關鍵幀的值。這下子咱們就瞭解animator在執行期間用來存儲和使用的數據結構。廢話一下,從PropertyValueHolder、KeyframeSet這個兩個類的源代碼來看,這三個類的API的設計挺有技巧的,他們都是經過將具備特定類型的實現做爲一個大的概況性的類的內部實現,經過這個大的抽象類提供對外的API(例如,PropertyValuesHolder.ofInt(...)的實現)。
不知道是否把你上面你是否能清楚,反正不太影響下面對ObjectAnimator.start()流程的分析。從上面一段的分析咱們瞭解到ValueAnimator.initAnimation()中的mValue是 PropertyValuesHolder類型的東西。在initAnimation()裏mValues[i].init()初始化它們的估值器Evaluator
void init() { if (mEvaluator == null) { // We already handle int and float automatically, but not their Object // equivalents mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null; } if (mEvaluator != null) { // KeyframeSet knows how to evaluate the common types - only give it a custom // evaluator if one has been set on this class mKeyframeSet.setEvaluator(mEvaluator); } }
mEvaluator固然也可使用ObjectAnimator.setEvaluator(...)傳入;爲空時,SDK根據mValueType爲咱們初始化特定類型的Evaluator。這樣咱們的初始化就完成了。接下來,跳出initAnimation()回到
setCurrentPlayTime(...)
public void setCurrentPlayTime(long playTime) { initAnimation(); long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { mSeekTime = playTime; mPlayingState = SEEKED; } mStartTime = currentTime - playTime; doAnimationFrame(currentTime); }
對animator三種狀態STOPPED、RUNNING、SEEKED的解釋
static final int STOPPED = 0; // 還沒開始播放
static final int RUNNING = 1; // 正常播放中
static final int SEEKED = 2; // 定位到一些時間值(Seeked to some time value)
對mSeekedTime、mStartTime的解釋
setCurrentPlayTime(...)中doAnimationFrame(currentTime) 以前的代碼其實都是對Animator的初始化。看來doAnimator(...)就是真正處理動畫幀的函數了。這個函數主要主要用來處理animator中的一幀,並在有必要的時候調整animator的開始時間。
final boolean doAnimationFrame(long frameTime) { //對animator的開始時間和狀態進行調整 if (mPlayingState == STOPPED) { mPlayingState = RUNNING; if (mSeekTime < 0) { mStartTime = frameTime; } else { mStartTime = frameTime - mSeekTime; // Now that we're playing, reset the seek time mSeekTime = -1; } } // The frame time might be before the start time during the first frame of // an animation. The "current time" must always be on or after the start // time to avoid animating frames at negative time intervals. In practice, this // is very rare and only happens when seeking backwards. final long currentTime = Math.max(frameTime, mStartTime); return animationFrame(currentTime); }
看來這個函數就是調整了一些參數,真正的處理函數還在animationFrame(...)中。咱們跟進去看看。
boolean animationFrame(long currentTime) { boolean done = false; switch (mPlayingState) { case RUNNING: case SEEKED: float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; 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); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
這個內部函數對給定的animation的一個簡單的動畫幀進行處理。currentTime這個參數是由定時脈衝(先不要了解這個定時脈衝是什麼,後面咱們會涉及)經過handler發送過來的(固然也多是初始化的時候,被程序調用的,就像咱們的分析過程同樣),它用於計算animation已運行的時間,以及已經運行分數值。這個函數的返回值標識這個animation是否應該中止(在運行時間超過animation應該運行的總時長的時候,包括重複次數超過的狀況)。
咱們能夠把這個函數裏面的fraction簡單地理解成animator的進度條的當前的位置。if (fraction >= 1f) 注意到函數裏面的這句話,當animator開始須要重複執行的時候,那麼就須要執行這個if判斷裏面的東西,這裏面主要就是記錄和改變重複執行animator的一些狀態和變量。爲了避免讓這篇文章太複雜,咱們這裏就不進行分析了。經過最簡單的animator只執行一次的狀況來分析。那麼接下來就應該執行animateValue(fraction)了。
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); } } }
在每個animator的幀,這個函數都會被調用,結合傳入的參數:已運行時間分數(fraction)。這個函數將已經運行的分數轉爲interpolaterd分數,而後轉化成一個可用於動畫的值(經過evaluator、這個函數一般在animation update的時候調用,可是它也可能在end()函數調用的時候被調用,用來設置property的最終值)。
在這裏咱們須要理清一下Interpolaterd和evaluator之間的關係。
咱們能夠把動畫的過程想象成是一部電影的播放,電影的播放中有進度條,Interpolator就是用來控制電影播放頻率,也就是快進快退要多少倍速。而後Evaluator根據Interpolator提供的值計算當前播放電影中的哪個畫面,也就是進度條要處於什麼位置。
這個函數分三步:
//PropertyValuesHolder.calculateValue(...) void calculateValue(float fraction) { mAnimatedValue = mKeyframeSet.getValue(fraction); } //mKeyframeSet.getValue public Object getValue(float fraction) { // Special-case optimization for the common case of only two keyframes if (mNumKeyframes == 2) { if (mInterpolator != null) { fraction = mInterpolator.getInterpolation(fraction); } return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), mLastKeyframe.getValue()); } if (fraction <= 0f) { final Keyframe nextKeyframe = mKeyframes.get(1); final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); if (interpolator != null) { fraction = interpolator.getInterpolation(fraction); } final float prevFraction = mFirstKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction); return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), nextKeyframe.getValue()); } else if (fraction >= 1f) { final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2); final TimeInterpolator interpolator = mLastKeyframe.getInterpolator(); if (interpolator != null) { fraction = interpolator.getInterpolation(fraction); } final float prevFraction = prevKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (mLastKeyframe.getFraction() - prevFraction); return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), mLastKeyframe.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(); if (interpolator != null) { fraction = interpolator.getInterpolation(fraction); } final float prevFraction = prevKeyframe.getFraction(); float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction); return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), nextKeyframe.getValue()); } prevKeyframe = nextKeyframe; } // shouldn't reach here return mLastKeyframe.getValue(); }
咱們先只關注if (mNumKeyframes == 2)這種狀況,由於這種狀況最多見。還記的咱們使用ObjectAnimator.ofInt(...)傳入的int[]數組嗎?咱們通常就傳入動畫開始值和結束值,也就是這裏的mNumKeyframes ==2 的狀況。這裏經過mEvaluator來計算,咱們看看代碼(IntEvaluator.evaluate(...)的代碼)。
public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); }
mEvaluator.evaluate(...)計算後,咱們就返回到ValueAnimator.animateValue(...)中,再回退到ValueAnimator.setCurrentPlayTime(...)。最後回到ValueAnimator.start(boolean playBackwards)。終於解析完了setCurrentPlayTime(...)這個函數,總結一下:這個函數主要用來初始化動畫的值,固然這個初始化比咱們想象中的複雜多了,它主要經過PropertyValuesHolder、Evaluator、Interpolator來進行值的初始化。PropertyValueHolder又經過KeyframeSet來存儲須要的值。
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) { // This sets the initial value of the animation, prior to actually starting it running setCurrentPlayTime(0); mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }
在setCurrentPlayTime(0)後,緊接着就經過notifyStartListeners()通知animation啓動的消息。最後經過animationHandler.start()去執行。animationHandler是一個AnimationHandler類型的對象,它實現了runable接口。
//AnimationHandler.start() public void start() { scheduleAnimation(); } //AnimationHandler.scheduleAnimation() private void scheduleAnimation() { if (!mAnimationScheduled) { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null); mAnimationScheduled = true; } } // Called by the Choreographer. @Override public void run() { mAnimationScheduled = false; doAnimationFrame(mChoreographer.getFrameTime()); }
mHandler.start()最終就是經過mChoreographer.發送給UI系統。這個過程比較複雜,這裏不介紹。咱們僅僅須要知道,動畫中的一幀經過這種方式發送給UI系統後,在UI系統執行完一幀後,又會回調AnimationHandler.run()。那麼其實這個過程就至關於,AnimationHandler.start()開始第一次動畫的執行→UI系統執行AnimationHandler.run()→UI系統執行完後,回調相關函數→再執行AnimationHandler.run().能夠理解爲AnimationHandler.run()會一直調用自身屢次(固然這是由UI系統驅動的),直至動畫結束。
這個過程比較複雜,若是你感興趣,能夠關注個人下一篇博客。