標籤:android動畫機制
現有的 Android 動畫架構是建立在 View 的層級上的,在 View 類中有一個介面 startAnimation 來使動畫開始,startAnimation 函數會將一個 Animation 類別的參數傳給 View,這個 Animation 是用來指定我們使用的是哪種動畫,現有的動畫有平移,縮放,旋轉以及 alpha 變換等。如果需要更複雜的效果,我們還可以將這些動畫組合起來,這些在下面會討論到。
要瞭解 Android 動畫是如何畫出來的,我們首先要瞭解 Android 的 View 是如何組織在一起,以及他們是如何畫自己的內容的。每一個視窗就是一棵 View 樹,下面以我們寫的 android_tabwidget_tutorial.doc 中的 tab 控制項的視窗為例,通過 android 工具 hierarchyviewer 得到的視窗 View Tree 如 所示:
圖 2. 介面 View 結構圖
圖 3. 介面 View 結構和顯示對應圖
其實這個圖不是完整的,沒有把 RootView 和 DecorView 畫出來,RootView 只有一個孩子就是 DecorView,這裡整個 View Tree 都是 DecorView 的子 View,它們是從 android1.5/frameworks/base/core/res/res/layout/screen_title.xml 這個 layout 檔案 infalte 出來的,感興趣的讀者可以參看 frameworks\policies\base\phone\com\android\internal\policy\Imp\PhoneWindow.java 中 generateLayout 函數部分的代碼。我們可以修改布局檔案和代碼來做一些比較 cool 的事情,如象 Windows 的縮小 / 關閉按鈕等。標題視窗以下部分的 FrameLayou 就是為了讓程式員通過 setContentView 來設定使用者需要的視窗內容。因為整個 View 的布局就是一棵樹,所以繪製的時候也是按照樹形結構遍曆來讓每個 View 進行繪製。ViewRoot.java 中的 draw 函數準備好 Canvas 後會調用 mView.draw(canvas),其中 mView 就是調用 ViewRoot.setView 時設定的 DecorView。然後看一下 View.java 中的 draw 函數:
遞迴的繪製整個視窗需要按順序執行以下幾個步驟:
- 繪製背景;
- 如果需要,儲存畫布(canvas)的層為淡入或淡出做準備;
- 繪製 View 本身的內容,通過調用 View.onDraw(canvas) 函數實現,通過這個我們應該能看出來 onDraw 函數重載的重要性,onDraw 函數中繪製線條 / 圓 / 文字等功能會調用 Canvas 中對應的功能。
- 繪製自己的孩子(通常也是一個 view 系統),通過 dispatchDraw(canvas) 實現,參看 ViewGroup.Java 中的代碼可知,dispatchDraw->drawChild->child.draw(canvas) 這樣的調用過程被用來保證每個子 View 的 draw 函數都被調用,通過這種遞迴調用從而讓整個 View 樹中的所有 View 的內容都得到繪製。在調用每個子 View 的 draw 函數之前,需要繪製的 View 的繪製位置是在 Canvas 通過 translate 函數調用來進行切換的,視窗中的所有 View 是共用一個 Canvas 對象
- 如果需要,繪製淡入淡出相關的內容並恢複儲存的畫布所在的層(layer)
- 繪製修飾的內容(例如捲軸)
當一個 ChildView 要重畫時,它會調用其成員函數 invalidate() 函數將通知其 ParentView 這個 ChildView 要重畫,這個過程一直向上遍曆到 ViewRoot,當 ViewRoot 收到這個通知後就會調用上面提到的 ViewRoot 中的 draw 函數從而完成繪製。View::onDraw() 有一個畫布參數 Canvas, 畫布顧名思義就是畫東西的地方,Android 會為每一個 View 設定好畫布,View 就可以調用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去畫內容。每一個 ChildView 的畫布是由其 ParentView 設定的,ParentView 根據 ChildView 在其內部的布局來調整 Canvas,其中畫布的屬性之一就是定義和 ChildView 相關的座標系,預設是橫軸為 X 軸,從左至右,值逐漸增大,豎軸為 Y 軸,從上至下,值逐漸增大 , 見 :
圖 4. 視窗座標系
Android 動畫就是通過 ParentView 來不斷調整 ChildView 的畫布座標系來實現的,下面以平移動畫來做樣本,見 5,假設在動畫開始時 ChildView 在 ParentView 中的初始位置在 (100,200) 處,這時 ParentView 會根據這個座標來設定 ChildView 的畫布,在 ParentView 的 dispatchDraw 中它發現 ChildView 有一個平移動畫,而且當前的平移位置是 (100, 200),於是它通過調用畫布的函數 traslate(100, 200) 來告訴 ChildView 在這個位置開始畫,這就是動畫的第一幀。如果 ParentView 發現 ChildView 有動畫,就會不斷的調用 invalidate() 這個函數,這樣就會導致自己會不斷的重畫,就會不斷的調用 dispatchDraw 這個函數,這樣就產生了動畫的後續幀,當再次進入 dispatchDraw 時,ParentView 根據平移動畫產生出第二幀的平移位置 (500, 200),然後繼續執行上述操作,然後產生第三幀,第四幀,直到動畫播完。具體演算法描述如清單 2:
清單 2. 演算法
[cpp] view plaincopy
- dispatchDraw()
- {
- ....
- Animation a = ChildView.getAnimation()
- Transformation tm = a.getTransformation();
- Use tm to set ChildView‘s Canvas;
- Invalidate();
- ....
- }
圖 5. 平移動畫
以上是以平移動畫為例子來說明動畫的產生過程,這其中又涉及到兩個重要的類型,Animation 和 Transformation,這兩個類是實現動畫的主要的類,Animation 中主要定義了動畫的一些屬性比如開始時間、期間、是否重複播放等,這個類主要有兩個重要的函數:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 會根據動畫的屬性來產生一系列的差值點,然後將這些差值點傳給 applyTransformation,這個函數將根據這些點來產生不同的 Transformation,Transformation 中包含一個矩陣和 alpha 值,矩陣是用來做平移、旋轉和縮放動畫的,而 alpha 值是用來做 alpha 動畫的(簡單理解的話,alpha 動畫相當於不斷變換透明度或顏色來實現動畫),以上面的平移矩陣為例子,當調用 dispatchDraw 時會調用 getTransformation 來得到當前的 Transformation,這個 Transformation 中的矩陣如下:
圖 6. 矩陣變換圖
所以具體的動畫只需要重載 applyTransformation 這個函數即可,類層次圖如下:
圖 7. 動畫類繼承關係圖
每個動畫都重載了父類的 applyTransformation 方法,這個方法會被父類的 getTransformation 方法調用。另外每個動畫還有個 initialize 方法,完成初始化工作。
使用者可以定義自己的動畫類,只需要繼承 Animation 類,然後重載 applyTransformation 這個函數。對動畫來說其行為主要靠差值點來決定的,比如,我們想開始動畫是逐漸加快的或者逐漸層慢的,或者先快後慢的,或者是勻速的,這些功能的實現主要是靠差值函數來實現的,Android 提供了 一個 Interpolator 的基類,你要實現什麼樣的速度可以重載其函數 getInterpolation,在 Animation 的 getTransformation 中產生差值點時,會用到這個函數。
圖 8. Interpolator 繼承關係圖
從上面的動畫機制的分析可知某一個 View 的動畫的繪製並不是由他自己完成的而是由它的父 view 完成,所有我們要注意上面 TextView 旋轉一周的動畫樣本程式中動畫的效果並不是由 TextView 來繪製的,而是由它的父 View 來做的。findViewById(R.id.TextView01).startAnimation(anim) 這個代碼其實是給這個 TextView 設定了一個 animation,而不是進行實際的動畫繪製,代碼如下 :
[cpp] view plaincopy
- public void startAnimation(Animation animation)
- {
-
- animation.setStartTime(Animation.START_ON_FIRST_FRAME);
-
- setAnimation(animation); invalidate();
-
- }
以上就是 Android 的動畫架構的原理,瞭解了原理對我們的開發來說就可以清晰的把握動畫的每一幀是怎樣產生的,這樣便於開發和調試。它把動畫的播放 / 繪製交給父 View 去處理而不是讓子 View 本身去繪製,這種從更高的層次上去控制的方式便於把動畫機製做成一個易用的架構,如果使用者要在某個 view 中使用動畫,只需要在 xml 描述檔案或代碼中指定就可以了,從而把動畫的實現和 View 本身內容的繪製(象 TextView 裡面的文字顯示)分離開了,起到了減少耦合和提高易用性的效果。