學習用的

Android UI Animationjava


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

Android 動畫使用示例

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

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

01 package com.ray.animation;
02  import android.app.Activity;
03  import android.os.Bundle;
04  import android.view.View;
05  import android.view.View.OnClickListener;
06  import android.view.animation.AccelerateDecelerateInterpolator;
07  import android.view.animation.Animation;
08  import android.view.animation.RotateAnimation;
09  import android.widget.Button;
10  public class TestAnimation extends Activity implements OnClickListener{
11      public void onCreate(Bundle savedInstanceState){
12          super.onCreate(savedInstanceState);
13          setContentView(R.layout.main);
14          Button btn =(Button)findViewById(R.id.Button);
15          btn.setOnClickListener(this);
16      }
17     public void onClick(View v){
18         Animation anim=null;
19         anim=new?RotateAnimation(0.0f,+360.0f);
20         anim.setInterpolator(new AccelerateDecelerateInterpolator());
21         anim.setDuration(3000);
22         findViewById(R.id.TextView01).startAnimation(anim);
23     }
24  }

使用 XML 文件方式,在打開 Eclipse 中新建的 Android 工程的 res 目錄中新建 anim 文件夾,而後在 anim 目錄中新建一個 myanim.xml( 注意文件名小寫 ),內容以下 :canvas


圖 1. 使用 xml 文件方式 
清單 2. 使用 xml 文件方式 
api

其中的 java 代碼以下:app

01 package com.ray.animation;
02  import android.app.Activity;
03  import android.os.Bundle;
04  import android.view.View;
05  import android.view.View.OnClickListener;
06  import android.view.animation.Animation;
07  import android.view.animation.AnimationUtils;
08  import android.widget.Button;
09  import android.widget.TextView;
10  public class TestAnimation extends Activity implements OnClickListener{
11  public void onCreate(Bundle savedInstanceState){
12  super.onCreate(savedInstanceState);
13  setContentView(R.layout.main);
14  Button btn =(Button)findViewById(R.id.Button01);
15  btn.setOnClickListener(this);
16  }
17
18  @Override
19  public void onClick(View v){
20  Animation anim=AnimationUtils.loadAnimation(this,R.anim.my_rotate_action);
21  findViewById(R.id.TextView01).startAnimation(anim);
22  }
23  }

Android 動畫框架原理

現有的 Android 動畫框架是創建在 View 的級別上的,在 View 類中有一個接口 startAnimation 來使動畫開始,startAnimation 函數會將一個 Animation 類別的參數傳給 View,這個 Animation 是用來指定咱們使用的是哪一種動畫,現有的動畫有平移,縮放,旋轉以及 alpha 變換等。若是須要更復雜的效果,咱們還能夠將這些動畫組合起來,這些在下面會討論到。框架

要了解 Android 動畫是如何畫出來的,咱們首先要了解 Android 的 View 是如何組織在一塊兒,以及他們是如何畫本身的內容的。每個窗口就是一棵 View 樹,下面以咱們寫的 android_tabwidget_tutorial.doc 中的 tab 控件的窗口爲例,經過 android 工具 hierarchyviewer 獲得的窗口 View Tree 以下圖 1 所示:ide


圖 2. 界面 View 結構圖 
界面 View 結構圖 

圖 3. 界面 View 結構和顯示對應圖 
界面 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 函數:

遞歸的繪製整個窗口須要按順序執行如下幾個步驟:

  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 動畫就是經過 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),而後繼續執行上述操做,而後產生第三幀,第四幀,直到動畫播完。具體算法描述如清單 2:

清單 2. 算法

1 dispatchDraw()
2  {
3      ....
4      Animation a = ChildView.getAnimation()
5      Transformation tm = a.getTransformation();
6      Use tm to set ChildView's Canvas;
7      Invalidate();
8      ....
9  }

圖 5. 平移動畫示意圖 
平移動畫示意圖 

以上是以平移動畫爲例子來講明動畫的產生過程,這其中又涉及到兩個重要的類型,Animation 和 Transformation,這兩個類是實現動畫的主要的類,Animation 中主要定義了動畫的一些屬性好比開始時間、持續時間、是否重複播放等,這個類主要有兩個重要的函數:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 會根據動畫的屬性來產生一系列的差值點,而後將這些差值點傳給 applyTransformation,這個函數將根據這些點來生成不一樣的 Transformation,Transformation 中包含一個矩陣和 alpha 值,矩陣是用來作平移、旋轉和縮放動畫的,而 alpha 值是用來作 alpha 動畫的(簡單理解的話,alpha 動畫至關於不斷變換透明度或顏色來實現動畫),以上面的平移矩陣爲例子,當調用 dispatchDraw 時會調用 getTransformation 來獲得當前的 Transformation,這個 Transformation 中的矩陣以下:


圖 6. 矩陣變換圖 
矩陣變換圖 

因此具體的動畫只須要重載 applyTransformation 這個函數便可,類層次圖以下:


圖 7. 動畫類繼承關係圖 
動畫類繼承關係圖 

用戶能夠定義本身的動畫類,只須要繼承 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 軸旋轉的動畫

01 package com.example.android.apis.animation;
02  import android.view.animation.Animation;
03  import android.view.animation.Transformation;
04  import android.graphics.Camera;
05  import android.graphics.Matrix;
06  /**
07  * An animation that rotates the view on the Y axis between two specified angles.
08  * This animation also adds a translation on the Z axis (depth) to improve the effect.
09  */
10  public class Rotate3dAnimation extends Animation {
11     private final float mFromDegrees;
12     private final float mToDegrees;
13     private final float mCenterX;
14     private final float mCenterY;
15     private final float mDepthZ;
16     private final boolean mReverse;
17     private Camera mCamera;
18     /**
19      * Creates a new 3D rotation on the Y axis. The rotation is defined by its
20      * start angle and its end angle. Both angles are in degrees. The rotation
21      * is performed around a center point on the 2D space, definied by a pair
22      * of X and Y coordinates, called centerX and centerY. When the animation
23      * starts, a translation on the Z axis (depth) is performed. The length
24      * of the translation can be specified, as well as whether the translation
25      * should be reversed in time.
26      *
27      * @param fromDegrees the start angle of the 3D rotation
28      * @param toDegrees the end angle of the 3D rotation
29      * @param centerX the X center of the 3D rotation
30      * @param centerY the Y center of the 3D rotation
31      * @param reverse true if the translation should be reversed, false otherwise
32      */
33     public Rotate3dAnimation(float fromDegrees, float toDegrees,
34             float centerX, float centerY, float depthZ, boolean reverse) {
35         mFromDegrees = fromDegrees;
36         mToDegrees = toDegrees;
37         mCenterX = centerX;
38         mCenterY = centerY;
39         mDepthZ = depthZ;
40         mReverse = reverse;
41     }
42
43     @Override
44     public void initialize(int width, int height, int parentWidth, int parentHeight) {
45         super.initialize(width, height, parentWidth, parentHeight);
46         mCamera = new Camera();
47     }
48
49     @Override
50     protected void applyTransformation(float interpolatedTime, Transformation t) {
51         final float fromDegrees = mFromDegrees;
52         float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
53         final float centerX = mCenterX;
54         final float centerY = mCenterY;
55         final Camera camera = mCamera;
56         final Matrix matrix = t.getMatrix();
57         camera.save();
58         if (mReverse) {
59             camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
60         else {
61             camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
62         }
63         camera.rotateY(degrees);
64         camera.getMatrix(matrix);
65         camera.restore();
66         matrix.preTranslate(-centerX, -centerY);
67         matrix.postTranslate(centerX, centerY);
68     }
69  }

在這個例子中咱們重載了 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 庫。

結束語

本文介紹了 Android 動畫框架的基本原理,能夠幫助開發者深刻理解 Android 的動畫是如何實現的,從而可以充分利用 android 現有框架來作出夠眩、夠酷的動畫效果。

原文地址:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-anmt1/

相關文章
相關標籤/搜索