最近在Android上做了一些動畫效果,網上查了一些資料,有各種各樣的使用方式,於是乘熱打鐵,想具體分析一下動畫是如何實現的,Animation, Animator都有哪些區別等等。
首先說Animation(android.view.animation.Animation)對象。
無論是用純java代碼構建Animation對象,還是通過xml文件定義Animation,其實最終的結果都是
Animation a = new AlphaAnimation(); Animation b = new ScaleAnimation(); Animation c = new RotateAnimation(); Animation d = new TranslateAnimation();
分別是透明度,縮放,旋轉,位移四種動畫效果。
而我們使用的時候,一般是用這樣的形式:
View.startAnimation(a);
那么就來看看View中的startAnimation()方法。
1.View.startAnimation(Animation)
先是調用View.setAnimation(Animation)方法給自己設置一個Animation對象,這個對象是View類中的一個名為mCurrentAnimation的成員變量。
然后它調用invalidate()來重繪自己。
我想,既然setAnimation()了,那么它要用的時候,肯定要getAnimation(),找到這個方法在哪里調用就好了。於是通過搜索,在View.draw(Canvas, ViewGroup, long)方法中發現了它的調用,代碼片段如下:
2.View.draw(Canvas, ViewGroup, long)
其中調用了View.drawAnimation()方法。
3.View.drawAnimation(ViewGroup, long, Animation, boolean)
代碼片段如下:
其中調用了Animation.getTransformation()方法。
4.Animation.getTransformation(long, Transformation, float)
該方法直接調用了兩個參數Animation.getTransformation()方法。
5.Animation.getTransformation(long, Transformation)
該方法先將參數currentTime處理成一個float表示當前動畫進度,比如說,一個2000ms的動畫,已經執行了1000ms了,那么進度就是0.5或者說50%。
然后將進度值傳入插值器(Interpolator)得到新的進度值,前者是均勻的,隨着時間是一個直線的線性關系,而通過插值器計算后得到的是一個曲線的關系。
然后將新的進度值和Transformation對象傳入applyTranformation()方法中。
6.Animation.applyTransformation(float, Transformation)
Animation的applyTransformation()方法是空實現,具體實現它的是Animation的四個子類,而該方法正是真正的處理動畫變化的過程。分別看下四個子類的applyTransformation()的實現。
ScaleAnimation
AlphaAnimation
RotateAnimation
TranslateAnimation
可見applyTransformation()方法就是動畫具體的實現,系統會以一個比較高的頻率來調用這個方法,一般情況下60FPS,是一個非常流暢的畫面了,也就是16ms,為了驗證這一點,我在applyTransformation方法中加入計算時間間隔並打印的代碼進行驗證,代碼如下:
最終得到的log如下圖所示:
右側是“手動”計算出來的時間差,有一定的波動,但大致上是16-17ms的樣子,左側是日志打印的時間,時間非常規則的相差20ms。
於是,根據以上的結果,可以得出以下內容:
1.首先證明了一點,Animation.applyTransformation()方法,是動畫具體的調用方法,我們可以覆寫這個方法,快速的制作自己的動畫。
2.另一點,為什么是16ms左右調用這個方法呢?是誰來控制這個頻率的?
對於以上的疑問,我有兩個猜測:
1.系統自己以postDelayed(this, 16)的形式調用的這個方法。具體的寫法,請參考《使用線程實現視圖平滑滾動》
2.系統一個死循環瘋狂的調用,運行一系列方法走到這個位置的間隔剛好是16ms左右,如果主線程卡了,這個間隔就變長了。
為了找到答案,我在Stack Overflow上發帖問了下,然后得到一個情報,那就是讓我去看看Choreographer(android.view.Choreographer)類。
1.Choreographer的構造方法
看了下Choreographer類的構造方法,是private的,不允許new外部類new,於是又發現了它有一個靜態的getInstance()方法,那么,我需要找到getInstance()方法被誰調用了,就可以知道Choreographer對象在什么地方被使用。一查,發現Choreographer.getInstance()在ViewRootImpl的構造方法中被調用。以下代碼是ViewRootImpl.ViewRootImpl(Context, Display)的片段。
OK,找到了ViewRootImpl中擁有一個mChoreographer對象,接下來,我需要去找,它如何被使用了,調用了它的哪些方法。於是發現如下代碼:
在scheduleTraversals()方法中,發現了這個對象的使用。
2.Choreographer.postCallback(int, Runnable, Object)
該方法輾轉調用了兩個內部方法,最終是調用了Choreographer.postCallbackDelayedInternal()方法。
3.Choreographer.postCallbackDelayedInternal(int, Object, Object, long)
這個方法中,
1.首先拿到當前的時間。
這里參數中有一個delay,它的值可以具體查看一下,你會發現它就是一個靜態常量,定義在Choreographer類中,它的值是10ms。也就是說,理想情況下,所有的時間都是以100FPS來運行的。
2.將要執行的內容加入到一個mCallbackQueues中。
3.然后執行scheduleFrameLocked()或者發送一個Message。
接着我們看Choreographer.scheduleFrameLocked(long)方法
4.Choreographer.scheduleFrameLocked(long)
if判斷進去的部分是是否使用垂直同步,暫時不考慮。
else進去的部分,還是將消息發送到mHandler對象中。那我們就直接來看mHandler對象就好了
5.Choreographer.FrameHandler
mHandler實例的類型是FrameHandler,它的定義就在Choreographer類中,代碼如下:
它的處理方法中有三個分支,但最終都會調用這個doFrame()方法。
6.Choreographer.doFrame()
doFrame()方法巴拉巴拉一大段,但在下面有非常工整的一段代碼,一下就吸引了我的眼球。
它調用了三次doCallbacks()方法,暫且不說這個方法是干什么的,但從它的第一個參數可以看到分別是輸入(INPUT),動畫(ANIMATION),遍歷(TRAVERSAL)。
於是,我先是看了下這三個常量的意義。下圖所示:
顯然,注釋是說:輸入事件最先處理,然后處理動畫,最后才處理view的布局和繪制。接下來我們看看Choreographer.doCallbacks()里面做了什么。
7.Choreographer.doCallbacks(int, long)
這個方法的操作非常統一,有三種不同類型的操作(輸入,動畫,遍歷),但在這里卻看不見這些具體事件的痕跡,這里我們不得不分析一下mCallbackQueues這個成員變量了。
mCallbackQueues是一個CallbackQueue對象數組。而它的下標,其意義並不是指元素1,元素2,元素3……而是指類型,請看上面doCallbacks()的代碼,參數callbackType傳給了mCallbackQueues[callbackType]中,而callbackType是什么呢?
其實就是前面說到的三個常量,CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_TRAVERVAL。
那么只需要根據不同的callbackType,就可以從這個數組里面取出不同類型的CallbackQueue對象來。
那么CallbackQueue又是什么呢?
CallbackQueue是Choreographer的一個內部類,其中我認為有兩個很重要的方法,分別是:extractDueCallbacksLocked(long)和addCallbackLocked(long, Object, Object)。
先說addCallbackLocked(long, Object, Object)。
1.CallbackQueue.addCallbackLocked(long, Object, Object)
首先它通過一個內部方法構建了一個CallbackRecord對象,然后后面的if判斷和while循環,大致上是將參數中的對象鏈接在CallbackRecord的尾部。其實CallbackRecord就是一個鏈表結構的對象。
2.CallbackQueue.extractDueCallbacksLocked(long)
這個方法是根據當前的時間,選出執行鏈表中與該時間最近的一個操作來處理,實際上,我們可以通俗的理解為“跳幀”。
想象一下,如果主線程運行的非常快速,非常流暢,每一步都能在10ms內准時運行到,那么我們的執行鏈表中的元素始終只有一個。
如果主線程中做了耗時操作,那么各種事件一直在往各自的鏈表中添加,但是當主線程有空來執行的時候,發現鏈表已經那么多積累的過期的事件了,那么就直接選擇最后一個來執行,那么界面上看起來,就是卡頓了一下。
到這里為止,我們得出以下結論:
1.控制外部輸入事件處理,動畫執行,UI變化都是在同一個類中做的處理,即是Choreographer,其中它規定的了理想的運行間隔為10ms,因為各種操作需要花費一定的時間,所以外部執行的間隔統計出來是大約16ms。
2.在Choreographer對象中有三條鏈表,分別保存着待處理的輸入事件,待處理的動畫事件,待處理的遍歷事件。
3.每次執行的時候,Choreographer會根據當前的時間,只處理事件鏈表中最后一個事件,當有耗時操作在主線程時,事件不能及時執行,就會出現所謂的“跳幀”,“卡頓”現象。
4.Choreographer的共有方法postCallback(callbackType, Object)是往事件鏈表中放事件的方法。而doFrame()是消耗這些事件的方法。
事到如今,已經探究出不少有用的細節。這里,又給自己提出一個問題,根據以上的事實,那么,只需要找到哪些東西再往這三條鏈表中放事件呢?
於是進一步探究一下。我們只需要找到postCallback()被哪些方法調用了即可。
於是請點擊這里,通過grepcode列舉了調用postCallback()的方法。
搞明白了Choreographer的工作原理,再去看ObjectAnimator,ValueAnimator的實現,就非常的輕松了。
ObjectAnimator.start()方法實際上是輾轉幾次調用了ValueAnimator的start()方法,ValueAnimator.start()又調用了一個臨時變量animationHandler.start()。
animationHandler實際上是一個Runnable,其中start()方法調用了scheduleAnimation()。
而這個方法:
調用了postCallback()方法。
將this(Runnable)post之后,實際上肯定就是要執行Runnable.run()方法
run()方法中又調用了doAnimationFrame()方法。這個方法具體的實現了動畫的某一幀的過程,然后再次調用了scheduleAnimation()方法。
就相當於postDelayed(this, 16)這種方式了。
到這里為止,對Animation原理的分析就到此結束了,本來只想分析下Animation的實現過程,沒想到順帶研究了一下Choreographer的工作原理,今天收獲還是不少。
其實還有好多疑問,技術學習也一天急不得,靠的是每日慢慢的積累,相信總有一天,各種疑惑都會迎刃而解。