天天看點

【ANDROID遊戲開發十四】深入ANIMATION,在SURFACEVIEW中照樣使用ANDROID—TWEEN ANIMATION!

———————————————————————

『很多童鞋說我的代碼運作後,點選home或者back後會程式異常,如果你也這樣遇到過,那麼你肯定沒有仔細讀完himi的博文,第十九篇himi專門寫了關于這些錯誤的原因和解決方法,這裡我在部落格都補充說明下,省的童鞋們總疑惑這一塊;請點選下面聯系進入閱讀:

——————————————————————-

之前在【android2d遊戲開發之四】中我給大家介紹了一張13幀的png的圖,利用設定可視區域的方式來實作動畫效果,但是這些屬于我們自己來實作動畫的方式,其實android給我們的有兩類自定義動畫方式:

第一類:frame by frame 幀動畫( 不推薦遊戲開發中使用)

所謂幀動畫,就是順序播放事先做好的圖像,類似于放電影;

分析: 此種方式類似我之前的那種利用設定可視區域的方式來實作動畫效果,不僅類似而且還不如!是以此種方式在此不予分析;

第二類:tween animation 漸變動畫

即通過對對象不斷做圖像變換(平移、縮放、旋轉)産生動畫效果!實作方式其實就是預先定義一組指令,這些指令指定了圖形變換的類型、觸發時間、持續時間。這些指令可以是以 xml 檔案方式定義,也可以是以源代碼方式定義。程式沿着時間線執行這些指令就可以實作動畫 效果。

總結:那麼在android 遊戲開發中我們優先選用兩種方式:第一種設定可視區域的方式來實作動畫效果(幀動畫),需要童鞋們手動實作,那麼在之前我的博文【android2d遊戲開發之四】中已經有了相應的源碼!大家可以去下載下傳研究;那麼這裡就主要為大家詳細分析 tween animation!

在講述surfaceview添加動畫之前,我們先來看看在view中如何實作tween animation以及tween 中的四種效果;

myviewanimation .java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

package com.himi.frameanimation;

import android.content.context;

import android.graphics.bitmap;

import android.graphics.bitmapfactory;

import android.graphics.canvas;

import android.graphics.color;

import android.graphics.paint;

import android.view.keyevent;

import android.view.view;

import android.view.animation.alphaanimation;

import android.view.animation.animation;

import android.view.animation.rotateanimation;

import android.view.animation.scaleanimation;

import android.view.animation.translateanimation;

/**

*@author himi

*@alphaanimation 漸變透明度動畫效果

*@scaleanimation 漸變尺寸伸縮動畫效果

*@translateanimation 畫面轉換位置移動動畫效果

*@rotateanimation 畫面轉移旋轉動畫效果

*/

public class myviewanimation extends view {

    private paint paint;

    private bitmap bmp;

    private int x = 50;

    private animation malphaanimation;

    private animation mscaleanimation;

    private animation mtranslateanimation;

    private animation mrotateanimation;

    public myviewanimation(context context) {

        super(context);

        paint = new paint();

        paint.setantialias(true);

        bmp = bitmapfactory.decoderesource(getresources(), r.drawable.icon);

        this.setfocusable(true);//隻有當該view獲得焦點時才會調用onkeydown方法

    }

    @override

    protected void ondraw(canvas canvas) {

        super.ondraw(canvas);

        canvas.drawcolor(color.black);

        paint.setcolor(color.white);

        canvas.drawtext("himi", x, 50, paint);//備注1

        canvas.drawtext("方向鍵↑ 漸變透明度動畫效果", 80, this.getheight() - 80, paint);

        canvas.drawtext("方向鍵↓ 漸變尺寸伸縮動畫效果", 80, this.getheight() - 60, paint);

        canvas.drawtext("方向鍵← 畫面轉換位置移動動畫效果", 80, this.getheight() - 40, paint);

        canvas.drawtext("方向鍵→ 畫面轉移旋轉動畫效果", 80, this.getheight() - 20, paint);

        canvas.drawbitmap(bmp, this.getwidth() / 2 - bmp.getwidth() / 2,

                this.getheight() / 2 - bmp.getheight() / 2, paint);

        x += 1;

    public boolean onkeydown(int keycode, keyevent event) {

        if (keycode == keyevent.keycode_dpad_up) {//漸變透明度動畫效果

            malphaanimation = new alphaanimation(0.1f, 1.0f);

            //第一個參數fromalpha 為動畫開始時候透明度

            //第二個參數toalpha 為動畫結束時候透明度

            //注意:取值範圍[0-1];[完全透明-完全不透明]

            malphaanimation.setduration(3000);

            ////設定時間持續時間為3000 毫秒=3秒

            this.startanimation(malphaanimation);

        } else if (keycode == keyevent.keycode_dpad_down) {//漸變尺寸伸縮動畫效果

            mscaleanimation = new scaleanimation(0.0f, 1.5f, 0.0f, 1.5f, animation

                    .relative_to_parent, 0.5f, animation.relative_to_parent, 0.0f);

            //第一個參數fromx為動畫起始時x坐标上的伸縮尺寸

            //第二個參數tox為動畫結束時x坐标上的伸縮尺寸

            //第三個參數fromy為動畫起始時y坐标上的伸縮尺寸

            //第四個參數toy 為動畫結束時y 坐标上的伸縮尺寸

            //注意:

            //0.0表示收縮到沒有

            //1.0表示正常無伸縮

            //值小于1.0表示收縮

            //值大于1.0表示放大

            //-----我這裡1-4參數表明是起始圖像大小不變,動畫終止的時候圖像被放大1.5倍

            //第五個參數pivotxtype 為動畫在x 軸相對于物件位置類型

            //第六個參數pivotxvalue 為動畫相對于物件的x 坐标的開始位置

            //第七個參數pivotxtype 為動畫在y 軸相對于物件位置類型

            //第八個參數pivotyvalue 為動畫相對于物件的y 坐标的開始位置

            //提示:位置類型有三種,每種效果大家自己嘗試哈~這裡偷下懶~

            //畢竟親眼看到效果的差別才記憶深刻~

            //animation.absolute 、animation.relative_to_self、animation.relative_to_parent

            mscaleanimation.setduration(2000);

            this.startanimation(mscaleanimation);

        } else if (keycode == keyevent.keycode_dpad_left) {//畫面轉換位置移動動畫效果

            mtranslateanimation = new translateanimation(0, 100, 0, 100);

            //第一個參數fromxdelta為動畫起始時x坐标上的移動位置

            //第二個參數toxdelta為動畫結束時x坐标上的移動位置

            //第三個參數fromydelta為動畫起始時y坐标上的移動位置

            //第四個參數toydelta 為動畫結束時y 坐标上的移動位置

            mtranslateanimation.setduration(2000);

            this.startanimation(mtranslateanimation);

        } else if (keycode == keyevent.keycode_dpad_right) {//畫面轉移旋轉動畫效果

            mrotateanimation = new rotateanimation(0.0f, 360.0f,

                animation.relative_to_self, 0.5f, animation.relative_to_self, 0.5f);

            //第一個參數fromdegrees為動畫起始時的旋轉角度

            //第二個參數todegrees 為動畫旋轉到的角度

            //第三個參數pivotxtype 為動畫在x 軸相對于物件位置類型

            //第四個參數pivotxvalue 為動畫相對于物件的x 坐标的開始位置

            //第五個參數pivotxtype 為動畫在y 軸相對于物件位置類型

            //第六個參數pivotyvalue 為動畫相對于物件的y 坐标的開始位置

            mrotateanimation.setduration(3000);

            this.startanimation(mrotateanimation);

        }

        return super.onkeydown(keycode, event);

}

補充:有童鞋說對三種相對位置不太了解,那麼我簡單說補充下:

//animation.absolute 相對位置是螢幕左上角,絕對位置! //animation.relative_to_self 相對位置是自身view;取值為0,是自身左上角,取值為1是自身的右下角; //animation.relative_to_parent 相對父類view的位置

當設定了位置類型之後,會讓你傳入x或者y的值,這裡的x,y可以了解成為一個點坐标!比如是旋轉動畫,那麼這個(x,y)就是旋轉中心點!

ok,對于tween animation下的每種動畫效果的執行個體化的每個參數都解釋的很詳細了!其實動畫的實作不光用代碼可以實作,在xml中注冊實作也是可以的,這裡就不多寫了,大家可以自己去嘗試寫一下,那麼在view中我們播放一種特效動畫,隻要執行個體化其對象,然後設定下參數,然後startanimation()就好了,步驟很簡單,隻是每個動畫執行個體化的參數确有着千變萬化的改法,這些我也沒法子一一來給大家示範,大家可以自己改改參數看看實際的效果!當然對于每種動畫我們不光有設定播放的時候,還有一些屬性和方法可以調用,比如animation.restart()重放動畫,gettransformation()此方法傳回假,說明動畫完成等等很多屬性,請各位童鞋自定實驗 o(∩_∩)o 哈哈~

順便先解釋下myviewanimation .java 類中ondraw()方法裡的(備注1)!其實這裡我是想跟大家說明下android animation實作機制

【啟動任意一種動畫效果之前 和 之後 的對比圖】

【ANDROID遊戲開發十四】深入ANIMATION,在SURFACEVIEW中照樣使用ANDROID—TWEEN ANIMATION!

很明顯、”himi”字樣在動畫開始前和開始後出現了移動,而且在myviewanimation.java中我沒有使用runnable接口,也沒有調用重新整理的函數,那麼我來給各位童鞋解釋下原因:

動畫的每種變換其實内部都是一次矩陣運算。在android 中,canvas 類中包含目前矩陣,當調用 canvas.drawbitmap (bmp, x, y, paint) 繪制時,android 會先把 bmp 做一次矩陣運算,然後将運算的結果顯示在 canvas 上,然後不斷修改 canvas 的矩陣并重新整理螢幕,view 裡的對象就會不停的做圖形變換,動畫就形成了。

還有一點提醒大家:動畫的播放是對整個遊戲畫布進行的操作,這一點要知道喲~

那麼下面就要給大家介紹如何在我們的surfaceview中運用tween animation!

mysurfaceviewanimation.java 

105

106

107

108

import android.util.log;

import android.view.surfaceholder;

import android.view.surfaceview;

import android.view.surfaceholder.callback;

public class mysurfaceviewanimation extends surfaceview implements callback, runnable {

    private thread th = new thread(this);

    private surfaceholder sfh;

    private canvas canvas;

    ///

private animation malphaanimation;

private animation mscaleanimation;

private animation mtranslateanimation;

private animation mrotateanimation;

public mysurfaceviewanimation(context context) {

    super(context);

    log.v("himi", "mysurfaceview");

    this.setkeepscreenon(true);

    bmp = bitmapfactory.decoderesource(getresources(), r.drawable.icon);

    sfh = this.getholder();

    sfh.addcallback(this);

    paint = new paint();

    paint.setantialias(true);

    setfocusable(true);

    setfocusableintouchmode(true);

//  this.setbackgroundresource(r.drawable.icon);//備注2

public void surfacecreated(surfaceholder holder) {

    log.v("himi", "surfacecreated");

    th.start();

public void draw() {

    try {

        canvas = sfh.lockcanvas();

        if (canvas != null) {

            canvas.drawcolor(color.black);

            paint.setcolor(color.white);

            canvas.drawtext("方向鍵↑ 漸變透明度動畫效果", 80, this.getheight() - 80, paint);

            canvas.drawtext("方向鍵↓ 漸變尺寸伸縮動畫效果", 80, this.getheight() - 60, paint);

            canvas.drawtext("方向鍵← 畫面轉換位置移動動畫效果", 80, this.getheight() - 40, paint);

            canvas.drawtext("方向鍵→ 畫面轉移旋轉動畫效果", 80, this.getheight() - 20, paint);

            canvas.drawbitmap(bmp, this.getwidth() / 2 - bmp.getwidth() / 2,

                    this.getheight() / 2 - bmp.getheight() / 2, paint);

    } catch (exception e) {

        log.v("himi", "draw is error!");

    } finally {

        sfh.unlockcanvasandpost(canvas);

@override

public boolean onkeydown(int keycode, keyevent event) {

    if (keycode == keyevent.keycode_dpad_up) {//漸變透明度動畫效果

        malphaanimation = new alphaanimation(0.1f, 1.0f);

        malphaanimation.setduration(3000);

        this.startanimation(malphaanimation);

    } else if (keycode == keyevent.keycode_dpad_down) {//漸變尺寸伸縮動畫效果

        mscaleanimation = new scaleanimation(0.0f, 2.0f,

                1.5f, 1.5f, animation.relative_to_parent,

                0.5f, animation.relative_to_parent, 0.0f);

        mscaleanimation.setduration(2000);

        this.startanimation(mscaleanimation);

    } else if (keycode == keyevent.keycode_dpad_left) {//畫面轉換位置移動動畫效果

        mtranslateanimation = new translateanimation(0, 100, 0, 100);

        mtranslateanimation.setduration(2000);

        this.startanimation(mtranslateanimation);

    } else if (keycode == keyevent.keycode_dpad_right) {//畫面轉移旋轉動畫效果

        mrotateanimation = new rotateanimation(0.0f, 360.0f,

        mrotateanimation.setduration(3000);

        this.startanimation(mrotateanimation);

    return super.onkeydown(keycode, event);

public void run() {

    // todo auto-generated method stub

    while (true) {

        draw();

        try {

            thread.sleep(100);

        } catch (exception ex) {

public void surfacechanged(surfaceholder holder, int format, int width, int height) {

    log.v("himi", "surfacechanged");

public void surfacedestroyed(surfaceholder holder) {

    log.v("himi", "surfacedestroyed");

動畫代碼實作跟view中的做法一樣,運作模拟器發現按鍵沒效果,不是按鍵沒觸發是本來就存在問題, – -。但是!大家可以把此類裡有一行,也就是(備注2)的注釋打開,我們給設定背景圖,然後在模拟器上的運作效果如下圖:

【ANDROID遊戲開發十四】深入ANIMATION,在SURFACEVIEW中照樣使用ANDROID—TWEEN ANIMATION!

很明顯的看到,我們的動畫正常運作了,雖然效果并不是我們想到的!但是這裡可以說明一點問題:

      surfaceview 本身具備雙緩沖機制!!!!!

有些文章裡說“給surfaceview添加雙緩沖”,其實是在畫蛇添足 – -,而且介紹的時候拿着單線程與雙線程例子來解釋雙緩沖更高效的實作方法;我想弱弱的問什麼是雙緩沖??? 如果surfaceview不具備雙緩沖,那敢問上面這張截圖如何解釋????

其實要實作雙緩沖,隻需要是建立一個bitmap和canvas,用這個建立的canvas把正弦波畫到建立的bitmap,畫完再通過sfh.lockcanvas擷取surfaceview對應的canvas,用這個canvas把建立的bitmap畫到surfaceview上去,這才叫雙緩沖; 還有雙緩存和多線程沒關系!

 那麼view中動畫的實作機制是在不斷的刷屏不斷的重複調用重寫的ondraw()方法、而在surfaceview的那張截圖确實也正常的動畫操作了,原因又何在?而且我們設定的背景圖覆寫我們draw出來的字型!!效果很不理想;那麼經過考慮我決定利用布局把view和surfaceview都一并顯示,用view主要去完成動畫部分,(那麼關于如何一并顯示,或者說同時在surfaceview中添加元件,在之前的【android 2d開發之六】 和 【android 2d開發之七】都有了詳細講解,那麼在這裡),當然一并顯示也會有問題,比如我們存在了view和surfaceiew,那麼按鍵的時候觸發的哪個?或者說如何去控制這兩個view?放心,我下面就跟大家一一來講解!

下面先讓我們把我們的view 和 surfaceview 先同時顯示出來:【黑色的是myview (view),白色是mysurfaceview(surfaceview)】

先上張運作截圖:  (圖4)

【ANDROID遊戲開發十四】深入ANIMATION,在SURFACEVIEW中照樣使用ANDROID—TWEEN ANIMATION!

main.xml中的代碼:

<?xml version="1.0" encoding="utf-8"?>

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

    <relativelayout

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_weight="1" >

       <com.himi.mysurfaceview android:id="@+id/view3d"

            android:layout_height="fill_parent"/>

    <com.himi.myview android:id="@+id/myview"

    </relativelayout>

</linearlayout>

xml中我們注冊了我們自定義的view-myview 和 surfaceview-mysurfaceview;

需要強調的有兩點:

1 : 當我們xml中注冊我們的view時,我們view類中的構造函數必須要用

      public myview(context context, attributeset attrs) {} 兩個參數的形式,以前的文章有講解。

2 : 當我們在xml中注冊兩個view的時候,它們顯示的次序就是根據xml注冊的順序來顯示,比如上面我們先注冊了mysurfaceview,然後注冊的myview ,那麼顯示的時候會把後添加進去的myview顯示在最上層!

下面我們來看mysurfaceview.java中的代碼:

package com.himi;

import android.util.attributeset;

*

* @author himi

public class mysurfaceview extends surfaceview implements callback, runnable {

    public static mysurfaceview msrv ;//----備注1

    private int move_x = 2, x = 20;

    private thread th;

    private paint p;

    public mysurfaceview(context context, attributeset attrs) {

        super(context, attrs);

        msrv=this;

        p = new paint();

        p.setantialias(true);

        sfh = this.getholder();

        sfh.addcallback(this);

        th = new thread(this);

        this.setkeepscreenon(true);

         this.setfocusable(true);// ----備注2

    public void surfacecreated(surfaceholder holder) {

        th.start();

    public void draw() {

        if(canvas!=null){

            canvas.drawcolor(color.white);

            canvas.drawtext("我是   - surfaceview", x + move_x, 280, p);

            sfh.unlockcanvasandpost(canvas);

    private void logic() {

        x += move_x;

        if (x > 200 || x < 80) {

            move_x = -move_x;

    public boolean onkeydown(int key, keyevent event) { //備注2

        return super.onkeydown(key, event);

    }  

    public void run() {

        // todo auto-generated method stub

        while (true) {

            draw();

            logic();

            try {

                thread.sleep(100);

            } catch (exception ex) {

            }

    public void surfacechanged(surfaceholder holder, int format, int width,

            int height) {

    public void surfacedestroyed(surfaceholder holder) {

代碼都很熟悉了, 主要我們來給大家解釋下備注1,備注2:

備注1:

   我在兩個myview 和 mysurfaceview中都定義了本類一個靜态對象,然後在初始化的時候都利用=this的形式進行了執行個體化;

注意:=this; 的這種執行個體形式要注意!隻能在目前程式中僅存在一個本類對象才可使用!

       為什麼要執行個體兩個view的執行個體而且定義成靜态,這樣做主要為了類之間友善調用和操作!比如在我們這個項目中,我這樣做是為了在mainactivity中去管理兩個view按鍵焦點!下面我會給出mainactivity的代碼,大家一看便知;

備注2:   

 我在兩個myview 和 mysurfaceview中都對擷取按鍵焦點注釋掉了,而是在别的類中的調用其view的靜态執行個體對象就可以任意類中對其設定!這樣就可以很容易去控制到底誰來響應按鍵了。

這裡還要強調一下:當xml中注冊多個 view的時候,當我們點選按鍵之後,android會先判定哪個view setfocusable(true)設定焦點了,如果都設定了,那麼android 會預設響應在xml中第一個注冊的view ,而不是兩個都會響應。那麼為什麼不同時響應呢?我解釋下:

【ANDROID遊戲開發十四】深入ANIMATION,在SURFACEVIEW中照樣使用ANDROID—TWEEN ANIMATION!

左面這截圖是android sdk api的樹狀圖,很明顯surfaceview繼承了view,它倆是基繼承關系,那麼不管是子類還是基類一旦響應了按鍵,其基類或者父類就不會再去響應;

下面我們來看mainactivity.java:

import android.app.activity;

import android.os.bundle;

import android.view.window;

import android.view.windowmanager;

public class mainactivity extends activity {

    /** called when the activity is first created. */

    public void oncreate(bundle savedinstancestate) {

        super.oncreate(savedinstancestate);

        this.requestwindowfeature(window.feature_no_title);

        this.getwindow().setflags(windowmanager.layoutparams.flag_fullscreen,

                windowmanager.layoutparams.flag_fullscreen);

        setcontentview(r.layout.main);

        mysurfaceview.msrv.setfocusable(false);//備注1

        myview.mv.setfocusable(true);//備注1

    public boolean onkeydown(int keycode, keyevent event) {//備注2

這裡是當程式運作的時候我們預設讓我們的myview(view)來響應按鍵。通過類名調用對應的view執行個體,然後設定擷取焦點的函數;

備注2:

    這裡要注意:不管你在xml中注冊了多少個view ,也不管view是否都設定了擷取焦點,隻要你在 mainactivity 中重寫onkeydown()函數,android 就會調用此函數。

那麼直接在surfaceview中進行實作動畫的想法這裡沒有得到很好的解決,而是我利用布局的方式來一同顯示的方式,希望各位童鞋如果有好的方法,在surfaceview中直接能使用動畫的建議和想法,希望留言給我,大家一起學習 讨論,謝謝 下面給出項目源碼:

繼續閱讀