Android 屬性動畫 原始碼解析 深入瞭解其內部實現

發表於2015-05-21

1、概述

Android中想做很炫酷的動畫效果,相信在很多時候你都可以選擇使用屬性動畫,關於屬性動畫如何使用,我們已經很詳細的寫過兩篇部落格講解。如果你還不瞭解,請參考:

Android 屬性動畫(Property Animation) 完全解析 (上)

Android 屬性動畫(Property Animation) 完全解析 (下)

本篇部落格將分析屬性動畫的實現原始碼,帶你深入的瞭解Android屬性動畫的內部實現機制。如果你經常用屬性動畫,但又一直沒有去檢視其原始碼實現,沒關係,請往下看。

2、分析前的猜想

在原始碼分析之前,我們需要有一個明確的思路,例如:原始碼的入口的選擇、甚至對其實現進行簡單的猜測,原始碼分析相當於一個驗證的過程,帶著一個目標去看原始碼,這樣的話,分析和理解起來更為方便。

對於實現屬性動畫,最常用的類就是ObjectAnimator了,只需要簡單的設定目標view,屬性,以及目標值等必要屬性,呼叫一下start();我們的動畫就完成了。

類似如下程式碼:

上述程式碼很好理解吧,設定動畫作用的view,作用的屬性,動畫開始、結束、以及中間的任意個屬性值;

然後是設定插值器,當然了插值器這個詞比較難理解,我要是說例如:AccelerateInterpolator、LinearInterpolator

然後設定估值演算法,這個看名字挺高階,其實內部實現尤其簡單:  return (int)(startInt + fraction * (endValue – startInt)); 開始值,加上當前的屬性改變的百分比*(結束-開始)

當然了,這個百分比是fraction ,其實就是上面的插值器算出來的。比如線性插值器:fraction 值就是currentTime – mStartTime) / mDuration,動畫的執行時間/總設定時間。

然後是設定動畫事件,

最後start()。

好了,現在我想問個問題,根據上面這些引數,如果我要你設計個屬性動畫框架,你怎麼做?

這個嘛,好整,拿到上述引數之後,start()中,開啟一個定時器,去執行一個任務;在任務內部,根據Interpolator計算出來的fraction,交給Evaluator,得到屬性當前應該設定的值,然後反射設定tagert的指定屬性,ok,奏事這麼簡單。嗯,大體上應該就是這樣,當然了,原始碼的實現肯定複雜很多,但是萬變不離其宗,所以接下來的原始碼閱讀,就是去驗證我們的這個答案。

3、原始碼分析

好了,猜想完了,我們就得進入驗證階段了~~

那麼,我們原始碼的入口就是上述程式碼了,不過貌似上述程式碼呼叫了好幾個方法,but,我覺得start之前的程式碼,無法是初始化例項,設定一些成員變數。

首先我們看ofInt,這裡為了簡單,我們的ofInt中的values引數,預設就一個,類似 .ofInt(view, “translationX”, 300) ;

1、ofInt

首先呼叫ObjectAnimator的構造方法傳入了一個target和propName,估計就是建立物件,然後舊路下target和propName,簡單看下

記錄完成target,propName以後,呼叫setIntValues

可以看到,把我們的propName,和values傳入到了一個PropertyValuesHolder的ofInt方法中,去構造一個PropertyValuesHolder物件,這個物件是幹什麼的呢?

從字面上看,是儲存view在動畫期間的屬性和值,記住是動畫期間的。繼續往下看:

可以看到在IntPropertyValuesHolder內部儲存了我們的propertyName;,然後又呼叫了setIntValues,儲存了我們的mValueType ,此外還存了一個mIntKeyframeSet。

這裡又出現一個新名詞,叫做mKeyframeSet,這個是由 KeyframeSet.ofInt(values);得到的。

那麼這個KeyframeSet是什麼呢?單純的理解是,Keyframe的集合,而Keyframe叫做關鍵幀,為一個動畫儲存time/value(時間與值)對。

那麼我們去看看它是如何通過KeyframeSet.ofInt(values);去構造與儲存的:

這裡程式碼跳躍比較大,部分程式碼我來解釋:

根據我們的values的長度,構造了keyframes陣列,然後分別通過Keyframe的ofInt方法,去構造keyframe物件,其實在內部:

就簡單存了一下fraction,和value;當然了,我們這裡values只有一個值,所以構造了兩個Keyframe。

拿到初始化完成的keyframes陣列以後,將其傳入了KeyframeSet的構造方法,初始化了KeyframeSet內部的一些成員變數。

存了有多少關鍵幀,開始幀,結束幀,以及插值器。

到此,我們的(PropertyValuesHolder.ofInt在徹底返回,可以看到這個過程中,我們成功的為PropertyValuesHolder物件賦值了propName,valueType,keyframeSet .

keyframeset中存了Keyframe集合,keyframe中儲存了(fraction , valuetype , value , hasValue)。

最後,叫 PropertyValuesHolder 交給我們的 ObjectAnimator的setValues方法。

首先記錄了mValues,注意這裡的values是PropertyValuesHolder型別的,然後通過一個mValueMap記錄:key為屬性的名稱,值為PropertyValuesHolder 。

好了,到此我們的ofInt結束了,暈否,其實還好。如果你暈了,我幫你總結下:ofInt就是記錄了target,propName,values(是將我們傳入的int型values,輾轉轉化成了PropertyValuesHolder),以及一個mValueMap,這個map的key是propName,value是PropertyValuesHolder,在PropertyValuesHolder內部又儲存了proprName, valueType , keyframeSet等等。

好了,接下來會輕鬆點,按照順序到setInterpolator了:

2、setInterpolator

沒撒說的,記錄下插值器,我們這裡也線性插值器,預設也是~~

然後是setEvaluator。

3、setEvaluator

記得我們這裡的mValue吧,在ofInt裡面初始化的,型別是PropertyValuesHolder。然後呼叫了PropertyValuesHolder.setEvalutor

記錄了一下估值演算法,然後再將其傳給KeyframeSet物件:

可以看到,我們把估值演算法,交給了PropertyValuesHolder以及KeyframeSet。

接下來,最後一個屬性,duration

4、setDuration

就是簡單在mDuration中記錄了一下動畫的持續時間,這個sDurationScale預設為1,貌似是用於調整,觀察動畫的,比如你可以調整為10,動畫就會慢10倍的播放。

好了,到此該設定的設定完成了,小小總結一下:

ofInt中例項化了一個ObjectAnimator物件,然後設定了target,propName,values(PropertyValuesHolder) ;然後分別在setInterpolator,setDuration設定了Interpolator和duration。其中setEvaluator是給values[0],以及keyframeSet設定估值演算法。

PropertyValueHolder實際上是IntPropertyValueHolder型別物件,包含propName,valueType,keyframeSet .

keyframeset中存了Keyframe集合,keyframe中儲存了(fraction , valuetype , value , hasValue)。

以上都比較簡單,關鍵就是看start()方法中,如何將這些屬性進行合理的處理呼叫神馬的。

5、start

喝杯水,小憩一下,準備征戰start()方法。

最終呼叫了ValueAnimator的statr(playBackwards)方法;

15-20行:設定了關於動畫的一些標誌位,mPlayingBackwards 表示動畫是否reverse;mCurrentIteration 記錄當前的動畫的執行次數(與setRepeatCount有關);mPlayingState 動畫的狀態為STOPPED;還有些其他的標誌位;

21行:生成一個AnimationHandler物件,getOrCreateAnimationHandler就是在當前執行緒變數ThreadLocal中取出來,沒有的話,則建立一個,然後set進去。

AnimationHandler中包含一些List集合用於儲存各種狀態的ValueAnimator。

22行:將當前ValueAnimator物件,加入  animationHandler.mPendingAnimations 集合。

23行:未設定mStartDelay,預設為0,則進入迴圈;

24行:  setCurrentPlayTime(0);一會需要細說

25-26行:設定些狀態。

27行:回撥監聽動畫的介面AnimatorListener的onAnimationStart方法,如果你設定了回撥監聽,此時就會進行回撥;

最後30行:呼叫animationHandler.start();需要細說;

好了,有兩個方法需要細說,首先看setCurrentPlayTime(0)

首先初始化動畫,然後得到當前的系統開始到現在的時間currentTime;設定mSeekTime,設定當前狀態為SEEKED;然後使用mSeekTime-playTime得到動畫現在需要執行的時間;最後呼叫 doAnimationFrame(currentTime),稍後看其程式碼;

關於initAnimation(),實際就是去設定我們ValueAnimator中儲存的mValues,也就是IntPropertyValueHolder的mEvaluator;

PropertyValuesHolder的init方法:

其實就是遍歷設定PropertyValuesHolder中的mEvaluator屬性,預設根據valueType進行判斷,IntEvaluator或者FloatEvaluator。

接下來應該看doAnimationFrame(currentTime);了

內部呼叫了:animationFrame(currentTime);

這裡通過判斷當前動畫的狀態,給出fraction,預設傳入的就是(float)(currentTime – mStartTime) / mDuration,動畫執行的時間除以總的時間比值;

接下來呼叫了animateValue(fraction)

在animateValue的內部,會將傳入的fraction,交給 mInterpolator.getInterpolation(fraction);方法,獲得插值器處理後的fraction;然後在將fraction交給估值演算法mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();進行計算得到當前時間點,屬性應該的值;最後會反射對我們設定的屬性進行設定。

終於看到,對我們的屬性的值進行設定了,偶也~~當然了,動畫如果沒結束,應該每隔一定的幀數,再次呼叫,嗯,的確是這樣的,你看到animationFrame最後是不是有個返回值,這個值會在fraction>=1的時候返回true;

我們還是先看看animateValue方法:

首先將fraction交給給 mInterpolator.getInterpolation(fraction);得到計算後的fraction;

然後for迴圈遍歷呼叫IntPropertyValueHolder的calculateValue方法:

在其內部,呼叫了mKeyframeSet的getValue,這裡注意我們的IntKeyFrameSet,千萬不要看錯方法了。

在其內部,因為我們只設定了一個目標屬性值,所以只有兩個關鍵幀;

然後16-20行,呼叫估值演算法的mEvaluator.evaluate方法,可以看到如果mEvaluator == null直接呼叫了firstValue + (int)(fraction * deltaValue);其實這個就是IntEvaluator的預設實現。

好了,for迴圈結束了,經過我們插值器和估值演算法得出的值,最終給了IntPropertyValueHolder的mIntAnimatedValue屬性;

回到animateValue方法:在animateValue的8-12行,繼續回撥動畫監聽onAnimationUpdate(this);方法;

animateValue的15-18行:迴圈拿到(其實我們就只有一個屬性)我們的IntPropertyValueHolder呼叫setAnimatedValue,進行反射為我們的屬性設定值,反射需要一些東西,比如target,propname,以及該屬性應該設定的值;這三個引數在哪呢?target作為引數傳入了,propName初始化的時候就設定了,至於該屬性應該設定的值,上面有一句:“ 好了,for迴圈結束了,經過我們插值器和估值演算法得出的值,最終給了IntPropertyValueHolder的mIntAnimatedValue屬性 ” 。是不是全了~~反射的程式碼就不貼了。

好了,到此,我們屬性動畫,設定的各種值,經過重重的計算作用到了我們的屬性上,反射修改了我們的屬性。到此我們已經完成了一大半,但是貌似還少了個,每隔多少幀呼叫一次~~

嗯,的確是的,跨度好大,現在回到我們的start方法,最後一行:呼叫animationHandler.start();這個還沒細說呢~~

animationHandler我們上面已經介紹了,儲存在當前執行緒的ThreadLocal裡面,裡面放了一些集合用於儲存各種狀態的ObjectAnimator,我們當前的ObjectAnimator物件也儲存在其mPendingAnimations的集合中(上面提到過~~)。

start內部最終呼叫了mChoreographer.postCallback,其中有一個引數是this;至於什麼是Choreographer,暫時不用管;但是你需要知道一件事,其實我們的animationHandler是Runnable的子類,而 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);類似與handler傳送訊息,最終執行這個Runnable的run方法。

說這麼多,其實就是一句話,這裡呼叫了animationHandler的 run方法。

6-20行:while迴圈,遍歷所有在mPendingAnimations中的ObjectAnimator,依次呼叫anim.startAnimation(this);

在anim.startAnimation(this);內部其實主要就一行程式碼:handler.mAnimations.add(this); 將當前動畫加入animationHandler的mAnimations集合;

26-29行:將animationHandler的mAnimations集合中的每個anim,加入到mTmpAnimations中;

30-35行:依次呼叫mTmpAnimations中的anim,anim.doAnimationFrame(frameTime)

doAnimationFrame(frameTime)上面已經分析過了,如果返回true,即doAnimationFrame的done為true,則將該動畫加入到結束動畫集合。

37-43行:迴圈呼叫mEndingAnims, mEndingAnims.get(i).endAnimation(this);內部,會將動畫移除mAnimations,回撥動畫監聽介面onAnimationEnd;以及重置各種標誌變數。
46-48行:如果mAnimations不為null,則再次呼叫scheduleAnimation();
哈哈,終於終於發現了,每隔多少幀呼叫一次動畫的地方了~~尼瑪這個scheduleAnimation,不就是animationHandler的 run方法呼叫的麼~~
前面已經描述過animationHandler的 run方法中通過計算屬性應該的值,反射設定;加上我們這裡的動畫沒結束,就會再次呼叫該run方法內部一致的方法~~~

搜噶,到此~~我們的屬性動畫的流程已經完美跑通了~~~

對了,看完以後,和我們文章開始的預期符合麼,其實我覺得差不多~~

4、總結

其實看原始碼的目的,最終就是為了總結,尼瑪這麼長的程式碼誰也記不住。。。所以看完記得總結:

ofInt中例項化了一個ObjectAnimator物件,然後設定了target,propName,values(PropertyValuesHolder) ;然後分別在setInterpolator,setDuration設定了Interpolator

和duration。其中setEvaluator是給PropertyValuesHolder,以及keyframeSet設定估值演算法。

PropertyValueHolder實際上是IntPropertyValueHolder型別物件,包含propName,valueType,keyframeSet .

keyframeset中存了Keyframe集合,keyframe中儲存了(fraction , valuetype , value , hasValue)。

上述其實都是設定各種值什麼的。真正核心要看start~

start()中:

首先,步驟1:更新動畫各種狀態,然後初步計算fraction為(currentTime – mStartTime) / mDuration;然後將這個fraction交給我們的插值器計算後得到新的fraction,再將新的fraction交給我們的估值演算法,估值演算法根據開始、結束、fraction得到當前屬性(動畫作用的屬性)應該的值,最大呼叫反射進行設定;

當然了:start中還會根據動畫的狀態,如果沒有結束,不斷的呼叫scheduleAnimation();該方法內部利用mChoreographer不斷的去重複我們的上述步驟1。

好了,順便說一句,在看原始碼的時候,一定要注意,你點進去的有可能不是真正執行時呼叫的,記得檢視該方法子類,比如我們檢視ObjectAnimator的方法,可能我們某個方法會跟到其父類ValueAnimator的方法,但是記得檢視ObjectAnimator是否複寫了該方法~~如果複寫了,你該看的應該是ObjectAnimator的方法~~~

相關文章