咖啡伴侶

          呆在上海
          posts - 163, comments - 156, trackbacks - 0, articles - 2

          android中使用2D動畫 — SurfaceView 轉載

          Posted on 2011-07-21 09:45 oathleo 閱讀(2929) 評論(0)  編輯  收藏 所屬分類: Android

          android中使用2D動畫 — SurfaceView

           

          通過之前介紹的如何自定義View, 我們知道使用它可以做一些簡單的動畫效果。它通過不斷循環的執行View.onDraw方法,每次執行都對內部顯示的圖形做一些調整,我們假設 onDraw方法每秒執行20次,這樣就會形成一個20幀的補間動畫效果。但是現實情況是你無法簡單的控制View.onDraw的執行幀數,這邊說的執 行幀數是指每秒View.onDraw方法被執行多少次,這是為什么呢?首先我們知道,onDraw方法是由系統幫我們調用的,我們是通過調用View的 invalidate方法通知系統需要重新繪制View,然后它就會調用View.onDraw方法。這些都是由系統幫我們實現的,所以我們很難精確去定 義View.onDraw的執行幀數,這個就是為什么我們這邊要了解SurfaceView了,它能彌補View的一些不足。

          首先我們先寫一個自定義View實現動畫效果,AnimateViewActivity.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
          package com.android777.demo.uicontroller.graphics;
           
          import android.app.Activity;
          import android.content.Context;
          import android.graphics.Canvas;
          import android.graphics.Color;
          import android.graphics.Paint;
          import android.os.Bundle;
          import android.view.View;
           
          public class AnimateViewActivity extends Activity {
           
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
           
                  setContentView(new AnimateView(this));
              }
           
              class AnimateView extends View{
           
                  float radius = 10;
                  Paint paint;
           
                  public AnimateView(Context context) {
                      super(context);
                      paint = new Paint();
                      paint.setColor(Color.YELLOW);
                      paint.setStyle(Paint.Style.STROKE);
                  }
           
                  @Override
                  protected void onDraw(Canvas canvas) {
           
                      canvas.translate(200, 200);
                      canvas.drawCircle(0, 0, radius++, paint);          
           
                      if(radius > 100){
                          radius = 10;
                      }
           
                      invalidate();//通過調用這個方法讓系統自動刷新視圖
           
                  }
           
              }
           
          }

          運行上面的Activity,你將看到一個圓圈,它原始半徑是10,然后不斷的變大,直到達到100后又恢復到10,這樣循環顯示,視覺效果上說你將看到一個逐漸變大的圓圈。

          上面就是一個簡單的自定義View實現的動畫效果,它能做的只是簡單的動畫效果,具有一些局限性。首先你無法控制動畫的顯示速度,目前它是以最快的 速度顯示,但是當你要更快,獲取幀數更高的動畫呢? 因為View的幀數是由系統控制的,所以你沒辦法完成上面的操作。如果你需要編寫一個游戲,它需要的幀數比較高,那么View就無能為力了,因為它被設計 出來時本來就不是用來處理一些高幀數顯示的。你可以把View理解為一個經過系統優化的,可以用來高效的執行一些幀數比較低動畫的對象,它具有特定的使用 場景,比如有一些幀數較低的游戲就可以使用它來完成:貪吃蛇、俄羅斯方塊、棋牌類等游戲,因為這些游戲執行的幀數都很低。但是如果是一些實時類的游戲,如 射擊游戲、塔防游戲、RPG游戲等就沒辦法使用View來做,因為它的幀數太低了,會導致動畫執行不順暢。所以我們需要一個能自己控制執行幀數的對 象,SurfaceView因此誕生了。

          什么是SurfaceView呢?

          為什么是SurfaceView呢?Surface的意思是表層,表面的意思,那么SurfaceView就是指一個在表層的View對象。為什么 說是在表層呢,這是因為它有點特殊跟其他View不一樣,其他View是繪制在表層外,而它就是充當表層對象。假設你要在一個球上畫畫,那么球的表層就當 做你的畫布對象,你畫的東西會擋住它的表層,我們默認沒使用SurfaceView,那么球的表層就是空白的,如果我們使用了SurfaceView,我 們可以理解為我們拿來的球本身表面就具有紋路,你是畫再紋路之上的,如果你畫的是半透明的,那么你將可以透過你畫的東西看到球面本身的紋路。SDK的文檔 說到:SurfaceView就是在Window上挖一個洞,它就是顯示在這個洞里,其他的View是顯示在Window上,所以View可以顯式在 SurfaceView之上,你也可以添加一些層在SurfaceView之上。

          SurfaceView還有其他的特性,上面我們講了它可以控制幀數,那它是什么控制的呢?這就需要了解它的使用機制。一般在很多游戲設計中,我們都是開辟一個后臺線程計算游戲相關的數據,然后根據這些計算完的新數據再刷新視圖對象,由于對View執行繪制操作只能在UI線程上, 所以當你在另外一個線程計算完數據后,你需要調用View.invalidate方法通知系統刷新View對象,所以游戲相關的數據也需要讓UI線程能訪 問到,這樣的設計架構比較復雜,要是能讓后臺計算的線程能直接訪問數據,然后更新View對象那改多好。我們知道View的更新只能在UI線程中,所以使 用自定義View沒辦法這么做,但是SurfaceView就可以了。它一個很好用的地方就是允許其他線程(不是UI線程)繪制圖形(使用Canvas),根據它這個特性,你就可以控制它的幀數,你如果讓這個線程1秒執行50次繪制,那么最后顯示的就是50幀。

           

          如何使用SurfaceView?

          首先SurfaceView也是一個View,它也有自己的生命周期。因為它需要另外一個線程來執行繪制操作,所以我們可以在它生命周期的初始化階 段開辟一個新線程,然后開始執行繪制,當生命周期的結束階段我們插入結束繪制線程的操作。這些是由其內部一個SurfaceHolder對象完成的。 SurfaceHolder,顧名思義,它里面保存了一個隊Surface對象的引用,而我們執行繪制方法就是操作這個 Surface,SurfaceHolder因為保存了對Surface的引用,所以使用它來處理Surface的生命周期,說到底 SurfaceView的生命周期其實就是Surface的生命周期,因為SurfaceHolder保存對Surface的引用,所以使用 SurfaceHolder來處理生命周期的初始化。首先我們先看看建立一個SurfaceView的大概步驟,先看看代碼:

          DemoSurfaceView.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
          package com.android777.demo.uicontroller.graphics;
           
          import android.content.Context;
          import android.view.SurfaceHolder;
          import android.view.SurfaceHolder.Callback;
          import android.view.SurfaceView;
           
          public class DemoSurfaceView extends SurfaceView  implements Callback{
           
              public DemoSurfaceView(Context context) {
                  super(context);
           
                  init(); //初始化,設置生命周期回調方法
           
              }
           
              private void init(){
           
                  SurfaceHolder holder = getHolder();
                  holder.addCallback(this); //設置Surface生命周期回調
           
              }
           
              @Override
              public void surfaceChanged(SurfaceHolder holder, int format, int width,
                      int height) {
              }
           
              @Override
              public void surfaceCreated(SurfaceHolder holder) {
              }
           
              @Override
              public void surfaceDestroyed(SurfaceHolder holder) {
              }
           
          }

          上面代碼我們在SurfaceView的構造方法中執行了init初始化方法,在這個方法里,我們先獲取SurfaceView里的 SurfaceHolder對象,然后通過它設置Surface的生命周期回調方法,使用DemoSurfaceView類本身作為回調方法代理類。 surfaceCreated方法,是當SurfaceView被顯示時會調用的方法,所以你需要再這邊開啟繪制的線 程,surfaceDestroyed方法是當SurfaceView被隱藏會銷毀時調用的方法,在這里你可以關閉繪制的線程。上面的例子運行后什么也不 顯示,因為還沒定義一個執行繪制的線程。下面我們修改下代碼,使用一個線程繪制一個逐漸變大的圓圈:

          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
          105
          106
          107
          108
          109
          110
          111
          112
          113
          114
          115
          package com.android777.demo.uicontroller.graphics;
           
          import android.content.Context;
          import android.graphics.Canvas;
          import android.graphics.Color;
          import android.graphics.Paint;
          import android.view.SurfaceHolder;
          import android.view.SurfaceHolder.Callback;
          import android.view.SurfaceView;
           
          public class DemoSurfaceView extends SurfaceView  implements Callback{
           
              LoopThread thread;
           
              public DemoSurfaceView(Context context) {
                  super(context);
           
                  init(); //初始化,設置生命周期回調方法
           
              }
           
              private void init(){
           
                  SurfaceHolder holder = getHolder();
                  holder.addCallback(this); //設置Surface生命周期回調
                  thread = new LoopThread(holder, getContext());
              }
           
              @Override
              public void surfaceChanged(SurfaceHolder holder, int format, int width,
                      int height) {
              }
           
              @Override
              public void surfaceCreated(SurfaceHolder holder) {
                  thread.isRunning = true;
                  thread.start();
              }
           
              @Override
              public void surfaceDestroyed(SurfaceHolder holder) {
                  thread.isRunning = false;
                  try {
                      thread.join();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
           
              /**
               * 執行繪制的繪制線程
               * @author Administrator
               *
               */
              class LoopThread extends Thread{
           
                  SurfaceHolder surfaceHolder;
                  Context context;
                  boolean isRunning;
                  float radius = 10f;
                  Paint paint;
           
                  public LoopThread(SurfaceHolder surfaceHolder,Context context){
           
                      this.surfaceHolder = surfaceHolder;
                      this.context = context;
                      isRunning = false;
           
                      paint = new Paint();
                      paint.setColor(Color.YELLOW);
                      paint.setStyle(Paint.Style.STROKE);
                  }
           
                  @Override
                  public void run() {
           
                      Canvas c = null;
           
                      while(isRunning){
           
                          try{
                              synchronized (surfaceHolder) {
           
                                  c = surfaceHolder.lockCanvas(null);
                                  doDraw(c);
                                  //通過它來控制幀數執行一次繪制后休息50ms
                                  Thread.sleep(50);
                              }
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          } finally {
                              surfaceHolder.unlockCanvasAndPost(c);
                          }
           
                      }
           
                  }
           
                  public void doDraw(Canvas c){
           
                      //這個很重要,清屏操作,清楚掉上次繪制的殘留圖像
                      c.drawColor(Color.BLACK);
           
                      c.translate(200, 200);
                      c.drawCircle(0,0, radius++, paint);
           
                      if(radius > 100){
                          radius = 10f;
                      }
           
                  }
           
              }
           
          }

          上面代碼編寫了一個使用SurfaceView制作的動畫效果,它的效果跟上面自定義View的一樣,但是這邊的SurfaceView可以控制動 畫的幀數。在SurfaceView中內置一個LoopThread線程,這個線程的作用就是用來繪制圖形,在SurfaceView中實例化一個 LoopThread實例,一般這個操作會放在SurfaceView的構造方法中。然后通過在SurfaceView中的SurfaceHolder的 生命周期回調方法中插入一些操作,當Surface被創建時(SurfaceView顯示在屏幕中時),開啟LoopThread執行繪 制,LoopThread會一直刷新SurfaceView對象,當SurfaceView被隱藏時就停止改線程釋放資源。這邊有幾個地方要注意下:

          1.因為SurfaceView允許自定義的線程操作Surface對象執行繪制方法,而你可能同時定義多個線程執行繪制,所以當你獲取 SurfaceHolder中的Canvas對象時記得加同步操作,避免兩個不同的線程同時操作同一個Canvas對象,當操作完成后記得調用 SurfaceHolder.unlockCanvasAndPost方法釋放掉Canvas鎖。

          2.在調用doDraw執行繪制時,因為SurfaceView的特點,它會保留之前繪制的圖形,所以你需要先清空掉上一次繪制時留下的圖形。(View則不會,它默認在調用View.onDraw方法時就自動清空掉視圖里的東西)。

          3. 記得在回調方法:onSurfaceDestroyed方法里將后臺執行繪制的LoopThread關閉,這里是使用join方法。這涉及到線程如何關閉 的問題,多數人建議是通過一個標志位:isRunning來判斷線程是否該停止運行,如果你想關閉線程只需要將isRunning改成false即可,線 程會自動執行完run方法后退出。

           

          總結:

          通過上面的分析,現在大家應該會簡單使用SurfaceView了,總的歸納起來SurfaceView和View不同之處有:

          1. SurfaceView允許其他線程更新視圖對象(執行繪制方法)而View不允許這么做,它只允許UI線程更新視圖對象。

          2. SurfaceView是放在其他最底層的視圖層次中,所有其他視圖層都在它上面,所以在它之上可以添加一些層,而且它不能是透明的。

          3. 它執行動畫的效率比View高,而且你可以控制幀數。

          4. 因為它的定義和使用比View復雜,占用的資源也比較多,除非使用View不能完成,再用SurfaceView否則最好用View就可以。(貪吃蛇,俄羅斯方塊,棋牌類這種幀數比較低的可以使用View做就好)

          主站蜘蛛池模板: 舞阳县| 栾川县| 太仆寺旗| 登封市| 乌拉特后旗| 周宁县| 同江市| 成都市| 东方市| 中宁县| 新竹县| 留坝县| 桃江县| 休宁县| 伊宁县| 内乡县| 南华县| 宁乡县| 瑞金市| 青铜峡市| 庐江县| 花莲市| 揭东县| 平顺县| 丘北县| 根河市| 利川市| 静安区| 重庆市| 吉林省| 林口县| 工布江达县| 乾安县| 建昌县| 奉化市| 湖北省| 蒙山县| 武冈市| 孝昌县| 连城县| 监利县|