天天看點

Android 動畫架構詳解

簡介: Android 平台提供了一套完整的動畫架構,使得開發者可以用它來開發各種動畫效果。Android 動畫架構詳解由原理篇和執行個體篇兩部分組成。本文是第一部分原理篇,主要分析 Tween 動畫的實作原理, 最後簡單介紹在 Android 中如何通過播放 Gif 檔案來實作動畫。第二部分執行個體篇将在原理篇的基礎上,向您展示一個動畫執行個體的實作。

Android 平台提供了一套完整的動畫架構,使得開發者可以用它來開發各種動畫效果,本文将向讀者闡述 Android 的動畫架構是如何實作的。任何一個架構都有其優勢和局限性,隻有明白了其實作原理,開發者才能知道哪些功能可以利用架構來實作,哪些功能須用其他途徑實作。Android 平台提供了兩類動畫,一類是 Tween 動畫,即通過對場景裡的對象不斷做圖像變換 ( 平移、縮放、旋轉 ) 産生動畫效果;第二類是 Frame 動畫,即順序播放事先做好的圖像,跟電影類似。本文是由兩部分組成的有關 Android 動畫架構詳解的第一部分原理篇, 主要分析 Tween 動畫的實作原理, 最後簡單介紹在 Android 中如何通過播放 Gif 檔案來實作動畫。我們先看一下動畫示例來一點感性認識。

Android動畫使用執行個體

使用動畫示例程式的效果是點選按鈕,TextView 旋轉一周。讀者也可以參看 Apidemos 中包 com.example.android.apis.animationview 下面的 Transition3d 和 com.example.android.apis.view 下面的 Animation1/Animation2/Animation3 示例代碼。

清單 1. 代碼直接使用動畫

package com.ray.animation; 
 import android.app.Activity; 
 import android.os.Bundle; 
 import android.view.View; 
 import android.view.View.OnClickListener; 
 import android.view.animation.AccelerateDecelerateInterpolator; 
 import android.view.animation.Animation; 
 import android.view.animation.RotateAnimation; 
 import android.widget.Button; 
 public class TestAnimation extends Activity implements OnClickListener{ 
     public void onCreate(Bundle savedInstanceState){ 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 
     Button btn =(Button)findViewById(R.id.Button); 
     btn.setOnClickListener(this); 
 } 
    public void onClick(View v){ 
    Animation anim=null; 
    anim=new?RotateAnimation(0.0f,+360.0f); 
    anim.setInterpolator(new AccelerateDecelerateInterpolator()); 
    anim.setDuration(3000); 
    findViewById(R.id.TextView01).startAnimation(anim); 
 } 
 } 
           

使用 XML 檔案方式,在打開 Eclipse 中建立的 Android 工程的 res 目錄中建立 anim 檔案夾,然後在 anim 目錄中建立一個 myanim.xml( 注意檔案名小寫 ),

Android 動畫架構詳解

其中的 java 代碼如下:

package com.ray.animation; 
 import android.app.Activity; 
 import android.os.Bundle; 
 import android.view.View; 
 import android.view.View.OnClickListener; 
 import android.view.animation.Animation; 
 import android.view.animation.AnimationUtils; 
 import android.widget.Button; 
 import android.widget.TextView; 
 public class TestAnimation extends Activity implements OnClickListener{ 
 public void onCreate(Bundle savedInstanceState){ 
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.main); 
 Button btn =(Button)findViewById(R.id.Button01); 
 btn.setOnClickListener(this); 
 } 

 @Override 
 public void onClick(View v){ 
 Animation anim=AnimationUtils.loadAnimation(this,R.anim.my_rotate_action); 
 findViewById(R.id.TextView01).startAnimation(anim); 
 } 
 } 
           

Android動畫原理架構

現有的 Android 動畫架構是建立在 View 的級别上的,在 View 類中有一個接口 startAnimation 來使動畫開始,startAnimation 函數會将一個 Animation 類别的參數傳給 View,這個 Animation 是用來指定我們使用的是哪種動畫,現有的動畫有平移,縮放,旋轉以及 alpha 變換等。如果需要更複雜的效果,我們還可以将這些動畫組合起來,這些在下面會讨論到。

要了解 Android 動畫是如何畫出來的,我們首先要了解 Android 的 View 是如何組織在一起,以及他們是如何畫自己的内容的。每一個視窗就是一棵 View 樹,下面以我們寫的 android_tabwidget_tutorial.doc 中的 tab 控件的視窗為例,通過 android 工具 hierarchyviewer 得到的視窗 View Tree 如下圖 1 所示:

圖2 ,界面View結構圖

Android 動畫架構詳解

圖3 ,界面View結構圖和顯示對應圖

Android 動畫架構詳解

其實這個圖不是完整的,沒有把 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 函數:

遞歸的繪制整個視窗需要按順序執行以下幾個步驟:

  1. 繪制背景;
  2. 如果需要,儲存畫布(canvas)的層為淡入或淡出做準備;
  3. 繪制 View 本身的内容,通過調用 View.onDraw(canvas) 函數實作,通過這個我們應該能看出來 onDraw 函數重載的重要性,onDraw 函數中繪制線條 / 圓 / 文字等功能會調用 Canvas 中對應的功能。下面我們會 drawLine 函數為例進行說明;
  4. 繪制自己的孩子(通常也是一個 view 系統),通過 dispatchDraw(canvas) 實作,參看 ViewGroup.Java 中的代碼可知,dispatchDraw->drawChild->child.draw(canvas) 這樣的調用過程被用來保證每個子 View 的 draw 函數都被調用,通過這種遞歸調用進而讓整個 View 樹中的所有 View 的内容都得到繪制。在調用每個子 View 的 draw 函數之前,需要繪制的 View 的繪制位置是在 Canvas 通過 translate 函數調用來進行切換的,視窗中的所有 View 是共用一個 Canvas 對象
  5. 如果需要,繪制淡入淡出相關的内容并恢複儲存的畫布所在的層(layer)
  6. 繪制修飾的内容(例如滾動條),這個可知要實作滾動條效果并不需要 ScrollView,可以在 View 中完成的,不過有一些小技巧,具體實作可以參看我們的 TextViewExample 示例代碼

當一個 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 動畫架構詳解

Android 動畫就是通過 ParentView 來不斷調整 ChildView 的畫布坐标系來實作的,下面以平移動畫來做示例,見下圖 4,假設在動畫開始時 ChildView 在 ParentView 中的初始位置在 (100,200) 處,這時 ParentView 會根據這個坐标來設定 ChildView 的畫布,在 ParentView 的 dispatchDraw 中它發現 ChildView 有一個平移動畫,而且目前的平移位置是 (100, 200),于是它通過調用畫布的函數 traslate(100, 200) 來告訴 ChildView 在這個位置開始畫,這就是動畫的第一幀。如果 ParentView 發現 ChildView 有動畫,就會不斷的調用 invalidate() 這個函數,這樣就會導緻自己會不斷的重畫,就會不斷的調用 dispatchDraw 這個函數,這樣就産生了動畫的後續幀,當再次進入 dispatchDraw 時,ParentView 根據平移動畫産生出第二幀的平移位置 (500, 200),然後繼續執行上述操作,然後産生第三幀,第四幀,直到動畫播完。

Android 動畫架構詳解

具體算法描述如清單 2:

清單 2. 算法

dispatchDraw() 
 { 
 .... 
 Animation a = ChildView.getAnimation() 
 Transformation tm = a.getTransformation(); 
 Use tm to set ChildView's Canvas; 
 Invalidate(); 
 .... 
 } 
           

圖5,平移動畫示意圖

Android 動畫架構詳解

以上是以平移動畫為例子來說明動畫的産生過程,這其中又涉及到兩個重要的類型,Animation 和 Transformation,這兩個類是實作動畫的主要的類,Animation 中主要定義了動畫的一些屬性比如開始時間、持續時間、是否重複播放等,這個類主要有兩個重要的函數:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 會根據動畫的屬性來産生一系列的內插補點點,然後将這些內插補點點傳給 applyTransformation,這個函數将根據這些點來生成不同的 Transformation,Transformation 中包含一個矩陣和 alpha 值,矩陣是用來做平移、旋轉和縮放動畫的,而 alpha 值是用來做 alpha 動畫的(簡單了解的話,alpha 動畫相當于不斷變換透明度或顔色來實作動畫),以上面的平移矩陣為例子,當調用 dispatchDraw 時會調用 getTransformation 來得到目前的 Transformation,這個 Transformation 中的矩陣如下:

圖6,矩陣變換圖

Android 動畫架構詳解

是以具體的動畫隻需要重載 applyTransformation 這個函數即可,類層次圖如下:

圖7,動畫類繼承關系圖

Android 動畫架構詳解

使用者可以定義自己的動畫類,隻需要繼承 Animation 類,然後重載 applyTransformation 這個函數。對動畫來說其行為主要靠內插補點點來決定的,比如,我們想開始動畫是逐漸加快的或者逐漸變慢的,或者先快後慢的,或者是勻速的,這些功能的實作主要是靠內插補點函數來實作的,Android 提供了 一個 Interpolator 的基類,你要實作什麼樣的速度可以重載其函數 getInterpolation,在 Animation 的 getTransformation 中生成內插補點點時,會用到這個函數。

從上面的動畫機制的分析可知某一個 View 的動畫的繪制并不是由他自己完成的而是由它的父 view 完成,所有我們要注意上面 TextView 旋轉一周的動畫示例程式中動畫的效果并不是由 TextView 來繪制的,而是由它的父 View 來做的。findViewById(R.id.TextView01).startAnimation(anim) 這個代碼其實是給這個 TextView 設定了一個 animation,而不是進行實際的動畫繪制,代碼如下 :

public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidate(); }

其他的動畫機制的代碼感興趣的讀者請自己閱讀,希望通過原理的講解以後看起來會輕松點,呵呵。

以上就是 Android 的動畫架構的原理,了解了原理對我們的開發來說就可以清晰的把握動畫的每一幀是怎樣生成的,這樣便于開發和調試。它把動畫的播放 / 繪制交給父 View 去處理而不是讓子 View 本身去繪制,這種從更高的層次上去控制的方式便于把動畫機制做成一個易用的架構,如果使用者要在某個 view 中使用動畫,隻需要在 xml 描述檔案或代碼中指定就可以了,進而把動畫的實作和 View 本身内容的繪制(象 TextView 裡面的文字顯示)分離開了,起到了減少耦合和提高易用性的效果。

動畫實作執行個體

在這個例子中,将要實作一個繞 Y 軸旋轉的動畫,這樣可以看到 3D 透視投影的效果,代碼如下 ( 清單 4):

清單 3. 實作一個繞 Y 軸旋轉的動畫

package com.example.android.apis.animation; 
 import android.view.animation.Animation; 
 import android.view.animation.Transformation; 
 import android.graphics.Camera; 
 import android.graphics.Matrix; 
 /** 
 * An animation that rotates the view on the Y axis between two specified angles. 
 * This animation also adds a translation on the Z axis (depth) to improve the effect. 
 */ 
 public class Rotate3dAnimation extends Animation { 
    private final float mFromDegrees; 
    private final float mToDegrees; 
    private final float mCenterX; 
    private final float mCenterY; 
    private final float mDepthZ; 
    private final boolean mReverse; 
    private Camera mCamera; 
    /** 
     * Creates a new 3D rotation on the Y axis. The rotation is defined by its 
     * start angle and its end angle. Both angles are in degrees. The rotation 
     * is performed around a center point on the 2D space, definied by a pair 
     * of X and Y coordinates, called centerX and centerY. When the animation 
     * starts, a translation on the Z axis (depth) is performed. The length 
     * of the translation can be specified, as well as whether the translation 
     * should be reversed in time. 
     * 
     * @param fromDegrees the start angle of the 3D rotation 
     * @param toDegrees the end angle of the 3D rotation 
     * @param centerX the X center of the 3D rotation 
     * @param centerY the Y center of the 3D rotation 
     * @param reverse true if the translation should be reversed, false otherwise 
     */ 
    public Rotate3dAnimation(float fromDegrees, float toDegrees, 
            float centerX, float centerY, float depthZ, boolean reverse) { 
        mFromDegrees = fromDegrees; 
        mToDegrees = toDegrees; 
        mCenterX = centerX; 
        mCenterY = centerY; 
        mDepthZ = depthZ; 
        mReverse = reverse; 
    } 

    @Override 
    public void initialize(int width, int height, int parentWidth, int parentHeight) { 
        super.initialize(width, height, parentWidth, parentHeight); 
        mCamera = new Camera(); 
    } 

    @Override 
    protected void applyTransformation(float interpolatedTime, Transformation t) { 
        final float fromDegrees = mFromDegrees; 
        float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); 
        final float centerX = mCenterX; 
        final float centerY = mCenterY; 
        final Camera camera = mCamera; 
        final Matrix matrix = t.getMatrix(); 
        camera.save(); 
        if (mReverse) { 
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); 
        } else { 
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); 
        } 
        camera.rotateY(degrees); 
        camera.getMatrix(matrix); 
        camera.restore(); 
        matrix.preTranslate(-centerX, -centerY); 
        matrix.postTranslate(centerX, centerY); 
    } 
 } 
           

在這個例子中我們重載了 applyTransformation 函數,interpolatedTime 就是 getTransformation 函 數傳下來的內插補點點,在這裡做了一個線性插值算法來生成中間角度:float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); Camera 類是用來實作繞 Y 軸旋轉後透視投影的,我們隻需要其傳回的 Matrix 值 , 這個值會賦給 Transformation 中的矩陣成員,當 ParentView 去為 ChildView 設定畫布時,就會用它來設定坐标系,這樣 ChildView 畫出來的效果就是一個繞 Y 軸旋轉同時帶有透視投影的效果。利用這個動畫便可以作出像立體翻頁等比較酷的效果。如何使用這個 animation 請見 ApiDemos 程式包 com.example.android.apis.animation 中的 Transition3d.java 代碼。

Android中顯示GiF格式圖

有關這一部分,本文将不做詳細介紹。 感興趣的讀者請參看 Apidemos 中 com.example.android.apis.graphics 下面的 BitmapDecode.java 中的示例代碼。

這裡先簡單說明一下,它的實作是通過 Movie 這個類來對 Gif 檔案進行讀取和解碼的,同時在 onDraw 函數中不斷的繪制每一幀圖檔完成的,這個示例代碼在 onDraw 中調用 invalidate 來反複讓 View 失效來讓系統不斷調用 SampleView 的 onDraw 函數;至于選出哪一幀圖檔進行繪制則是傳入系統目前時間給 Movie 類,然後讓它根據時間順序來選出幀圖檔。反複讓 View 失效的方式比較耗資源,繪制效果允許的話可以采取延時讓 View 失效的方式來減小 CPU 消耗。

目前使用這個方式播放一些 Gif 格式的動畫時會出現花屏的現象,這是因為 Android 中使用的 libgif 庫是比較老的版本,新的 tag 不支援,是以導緻花屏,解決辦法有制作 Gif 圖檔時别使用太新的 tag 或完善 android 中對應的 libgif 庫。

引用:

http://www.ibm.com/developerworks/cn/opensource/os-cn-android-anmt1/

http://www.ibm.com/developerworks/cn/opensource/os-cn-android-anmt2/

http://blog.csdn.net/banketree/article/details/25773243(圖方式展示各個動畫類的詳細内容)

http://www.w2bc.com/Article/10394

繼續閱讀