android中使用2D動畫 — SurfaceView 轉(zhuǎn)載
Posted on 2011-07-21 09:45 oathleo 閱讀(2929) 評論(0) 編輯 收藏 所屬分類: Androidandroid中使用2D動畫 — SurfaceView
通過之前介紹的如何自定義View, 我們知道使用它可以做一些簡單的動畫效果。它通過不斷循環(huán)的執(zhí)行View.onDraw方法,每次執(zhí)行都對內(nèi)部顯示的圖形做一些調(diào)整,我們假設(shè) onDraw方法每秒執(zhí)行20次,這樣就會形成一個20幀的補間動畫效果。但是現(xiàn)實情況是你無法簡單的控制View.onDraw的執(zhí)行幀數(shù),這邊說的執(zhí) 行幀數(shù)是指每秒View.onDraw方法被執(zhí)行多少次,這是為什么呢?首先我們知道,onDraw方法是由系統(tǒng)幫我們調(diào)用的,我們是通過調(diào)用View的 invalidate方法通知系統(tǒng)需要重新繪制View,然后它就會調(diào)用View.onDraw方法。這些都是由系統(tǒng)幫我們實現(xiàn)的,所以我們很難精確去定 義View.onDraw的執(zhí)行幀數(shù),這個就是為什么我們這邊要了解SurfaceView了,它能彌補View的一些不足。
首先我們先寫一個自定義View實現(xiàn)動畫效果,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(); //通過調(diào)用這個方法讓系統(tǒng)自動刷新視圖 } } } |
運行上面的Activity,你將看到一個圓圈,它原始半徑是10,然后不斷的變大,直到達到100后又恢復(fù)到10,這樣循環(huán)顯示,視覺效果上說你將看到一個逐漸變大的圓圈。
上面就是一個簡單的自定義View實現(xiàn)的動畫效果,它能做的只是簡單的動畫效果,具有一些局限性。首先你無法控制動畫的顯示速度,目前它是以最快的 速度顯示,但是當(dāng)你要更快,獲取幀數(shù)更高的動畫呢? 因為View的幀數(shù)是由系統(tǒng)控制的,所以你沒辦法完成上面的操作。如果你需要編寫一個游戲,它需要的幀數(shù)比較高,那么View就無能為力了,因為它被設(shè)計 出來時本來就不是用來處理一些高幀數(shù)顯示的。你可以把View理解為一個經(jīng)過系統(tǒng)優(yōu)化的,可以用來高效的執(zhí)行一些幀數(shù)比較低動畫的對象,它具有特定的使用 場景,比如有一些幀數(shù)較低的游戲就可以使用它來完成:貪吃蛇、俄羅斯方塊、棋牌類等游戲,因為這些游戲執(zhí)行的幀數(shù)都很低。但是如果是一些實時類的游戲,如 射擊游戲、塔防游戲、RPG游戲等就沒辦法使用View來做,因為它的幀數(shù)太低了,會導(dǎo)致動畫執(zhí)行不順暢。所以我們需要一個能自己控制執(zhí)行幀數(shù)的對 象,SurfaceView因此誕生了。
什么是SurfaceView呢?
為什么是SurfaceView呢?Surface的意思是表層,表面的意思,那么SurfaceView就是指一個在表層的View對象。為什么 說是在表層呢,這是因為它有點特殊跟其他View不一樣,其他View是繪制在表層外,而它就是充當(dāng)表層對象。假設(shè)你要在一個球上畫畫,那么球的表層就當(dāng) 做你的畫布對象,你畫的東西會擋住它的表層,我們默認沒使用SurfaceView,那么球的表層就是空白的,如果我們使用了SurfaceView,我 們可以理解為我們拿來的球本身表面就具有紋路,你是畫再紋路之上的,如果你畫的是半透明的,那么你將可以透過你畫的東西看到球面本身的紋路。SDK的文檔 說到:SurfaceView就是在Window上挖一個洞,它就是顯示在這個洞里,其他的View是顯示在Window上,所以View可以顯式在 SurfaceView之上,你也可以添加一些層在SurfaceView之上。
SurfaceView還有其他的特性,上面我們講了它可以控制幀數(shù),那它是什么控制的呢?這就需要了解它的使用機制。一般在很多游戲設(shè)計中,我們都是開辟一個后臺線程計算游戲相關(guān)的數(shù)據(jù),然后根據(jù)這些計算完的新數(shù)據(jù)再刷新視圖對象,由于對View執(zhí)行繪制操作只能在UI線程上, 所以當(dāng)你在另外一個線程計算完數(shù)據(jù)后,你需要調(diào)用View.invalidate方法通知系統(tǒng)刷新View對象,所以游戲相關(guān)的數(shù)據(jù)也需要讓UI線程能訪 問到,這樣的設(shè)計架構(gòu)比較復(fù)雜,要是能讓后臺計算的線程能直接訪問數(shù)據(jù),然后更新View對象那改多好。我們知道View的更新只能在UI線程中,所以使 用自定義View沒辦法這么做,但是SurfaceView就可以了。它一個很好用的地方就是允許其他線程(不是UI線程)繪制圖形(使用Canvas),根據(jù)它這個特性,你就可以控制它的幀數(shù),你如果讓這個線程1秒執(zhí)行50次繪制,那么最后顯示的就是50幀。
如何使用SurfaceView?
首先SurfaceView也是一個View,它也有自己的生命周期。因為它需要另外一個線程來執(zhí)行繪制操作,所以我們可以在它生命周期的初始化階 段開辟一個新線程,然后開始執(zhí)行繪制,當(dāng)生命周期的結(jié)束階段我們插入結(jié)束繪制線程的操作。這些是由其內(nèi)部一個SurfaceHolder對象完成的。 SurfaceHolder,顧名思義,它里面保存了一個隊Surface對象的引用,而我們執(zhí)行繪制方法就是操作這個 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(); //初始化,設(shè)置生命周期回調(diào)方法 } private void init(){ SurfaceHolder holder = getHolder(); holder.addCallback( this ); //設(shè)置Surface生命周期回調(diào) } @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的構(gòu)造方法中執(zhí)行了init初始化方法,在這個方法里,我們先獲取SurfaceView里的 SurfaceHolder對象,然后通過它設(shè)置Surface的生命周期回調(diào)方法,使用DemoSurfaceView類本身作為回調(diào)方法代理類。 surfaceCreated方法,是當(dāng)SurfaceView被顯示時會調(diào)用的方法,所以你需要再這邊開啟繪制的線 程,surfaceDestroyed方法是當(dāng)SurfaceView被隱藏會銷毀時調(diào)用的方法,在這里你可以關(guān)閉繪制的線程。上面的例子運行后什么也不 顯示,因為還沒定義一個執(zhí)行繪制的線程。下面我們修改下代碼,使用一個線程繪制一個逐漸變大的圓圈:
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(); //初始化,設(shè)置生命周期回調(diào)方法 } private void init(){ SurfaceHolder holder = getHolder(); holder.addCallback( this ); //設(shè)置Surface生命周期回調(diào) 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(); } } /** * 執(zhí)行繪制的繪制線程 * @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); //通過它來控制幀數(shù)執(zhí)行一次繪制后休息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可以控制動 畫的幀數(shù)。在SurfaceView中內(nèi)置一個LoopThread線程,這個線程的作用就是用來繪制圖形,在SurfaceView中實例化一個 LoopThread實例,一般這個操作會放在SurfaceView的構(gòu)造方法中。然后通過在SurfaceView中的SurfaceHolder的 生命周期回調(diào)方法中插入一些操作,當(dāng)Surface被創(chuàng)建時(SurfaceView顯示在屏幕中時),開啟LoopThread執(zhí)行繪 制,LoopThread會一直刷新SurfaceView對象,當(dāng)SurfaceView被隱藏時就停止改線程釋放資源。這邊有幾個地方要注意下:
1.因為SurfaceView允許自定義的線程操作Surface對象執(zhí)行繪制方法,而你可能同時定義多個線程執(zhí)行繪制,所以當(dāng)你獲取 SurfaceHolder中的Canvas對象時記得加同步操作,避免兩個不同的線程同時操作同一個Canvas對象,當(dāng)操作完成后記得調(diào)用 SurfaceHolder.unlockCanvasAndPost方法釋放掉Canvas鎖。
2.在調(diào)用doDraw執(zhí)行繪制時,因為SurfaceView的特點,它會保留之前繪制的圖形,所以你需要先清空掉上一次繪制時留下的圖形。(View則不會,它默認在調(diào)用View.onDraw方法時就自動清空掉視圖里的東西)。
3. 記得在回調(diào)方法:onSurfaceDestroyed方法里將后臺執(zhí)行繪制的LoopThread關(guān)閉,這里是使用join方法。這涉及到線程如何關(guān)閉 的問題,多數(shù)人建議是通過一個標志位:isRunning來判斷線程是否該停止運行,如果你想關(guān)閉線程只需要將isRunning改成false即可,線 程會自動執(zhí)行完run方法后退出。
總結(jié):
通過上面的分析,現(xiàn)在大家應(yīng)該會簡單使用SurfaceView了,總的歸納起來SurfaceView和View不同之處有:
1. SurfaceView允許其他線程更新視圖對象(執(zhí)行繪制方法)而View不允許這么做,它只允許UI線程更新視圖對象。
2. SurfaceView是放在其他最底層的視圖層次中,所有其他視圖層都在它上面,所以在它之上可以添加一些層,而且它不能是透明的。
3. 它執(zhí)行動畫的效率比View高,而且你可以控制幀數(shù)。
4. 因為它的定義和使用比View復(fù)雜,占用的資源也比較多,除非使用View不能完成,再用SurfaceView否則最好用View就可以。(貪吃蛇,俄羅斯方塊,棋牌類這種幀數(shù)比較低的可以使用View做就好)