李華明Himi 原創,轉載務必在明顯處註明:
不少童鞋說個人代碼運行後,點擊home或者back後會程序異常,若是你也這樣遇到過,那麼你確定沒有仔細讀完Himi的博文,第十九篇Himi專門寫了關於這些錯誤的緣由和解決方法,這裏我在博客都補充說明下,省的童鞋們總疑惑這一塊;請點擊下面聯繫進入閱讀:css
【Android遊戲開發十九】(必看篇)SurfaceView運行機制詳解—剖析Back與Home按鍵及切入後臺等異常處理!html
以前在【Android2D遊戲開發之四】中我給你們介紹了一張13幀的png的圖,利用設置可視區域的方式來實現動畫效果,可是這些屬於咱們本身來實現動畫的方式,其實Android給咱們的有兩類自定義動畫方式:java
第一類:Frame By Frame 幀動畫( 不推薦遊戲開發中使用)android
所謂幀動畫,就是順序播放事先作好的圖像,相似於放電影;canvas
分析: 此種方式相似我以前的那種利用設置可視區域的方式來實現動畫效果,不只相似並且還不如!因此此種方式在此不予分析;緩存
第二類:Tween Animation 漸變更畫多線程
即經過對對象不斷作圖像變換(平移、縮放、旋轉)產生動畫效果!實現方式其實就是預先定義一組指令,這些指令指定了圖形變換的類型、觸發時間、持續時間。這些指令能夠是以 XML 文件方式定義,也能夠是以源代碼方式定義。程序沿着時間線執行這些指令就能夠實現動畫 效果。app
總結:那麼在Android 遊戲開發中咱們優先選用兩種方式:第一種設置可視區域的方式來實現動畫效果(幀動畫),須要童鞋們手動實現,那麼在以前個人博文【Android2D遊戲開發之四】中已經有了相應的源碼!你們能夠去下載研究;那麼這裏就主要爲你們詳細分析 Tween Animation!ide
在講述SurfaceView添加動畫以前,咱們先來看看在View中如何實現Tween Animation以及Tween 中的四種效果;函數
MyViewAnimation .java
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實現機制
【啓動任意一種動畫效果以前 和 以後 的對比圖】
很明顯、"Himi"字樣在動畫開始前和開始後出現了移動,並且在MyViewAnimation.java中我沒有使用Runnable接口,也沒有調用刷新的函數,那麼我來給各位童鞋解釋下緣由:
動畫的每種變換其實內部都是一次矩陣運算。在Android 中,Canvas 類中包含當前矩陣,當調用 Canvas.drawBitmap (bmp, x, y, Paint) 繪製時,android 會先把 bmp 作一次矩陣運算,而後將運算的結果顯示在 Canvas 上,而後不斷修改 Canvas 的矩陣並刷新屏幕,View 裏的對象就會不停的作圖形變換,動畫就造成了。
還有一點提醒你們:動畫的播放是對整個遊戲畫布進行的操做,這一點要知道喲~
那麼下面就要給你們介紹如何在咱們的SurfaceView中運用Tween Animation!
MySurfaceViewAnimation.java
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.util.Log; import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.SurfaceHolder.Callback; 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 */ public class MySurfaceViewAnimation extends SurfaceView implements Callback, Runnable { private Thread th = new Thread(this); private SurfaceHolder sfh; private Canvas canvas; private Paint paint; private Bitmap bmp; /// 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, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 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)的註釋打開,咱們給設置背景圖,而後在模擬器上的運行效果以下圖:
很明顯的看到,咱們的動畫正常運行了,雖然效果並非咱們想到的!可是這裏能夠說明一點問題:
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)
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_width="fill_parent" android:layout_height="fill_parent"/> <com.himi.MyView android:id="@+id/myview" android:layout_width="fill_parent" android:layout_height="fill_parent"/> </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.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.SurfaceHolder.Callback; /** * * @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 SurfaceHolder sfh; private Canvas canvas; 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() { canvas = sfh.lockCanvas(); 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; } } @Override 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 SDK Api的樹狀圖,很明顯SurfaceView繼承了View,它倆是基繼承關係,那麼不論是子類仍是基類一旦響應了按鍵,其基類或者父類就不會再去響應;
下面咱們來看MainActivity.java:
package com.himi; import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; import android.view.Window; import android.view.WindowManager; /** * * @author Himi * */ public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override 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 } @Override public boolean onKeyDown(int keyCode, KeyEvent event) {//備註2 return super.onKeyDown(keyCode, event); } }
備註1:
這裏是當程序運行的時候咱們默認讓咱們的MyView(View)來響應按鍵。經過類名調用對應的View實例,而後設置獲取焦點的函數;
備註2:
這裏要注意:無論你在xml中註冊了多少個View ,也無論View是否都設置了獲取焦點,只要你在 MainActivity 中重寫onKeyDown()函數,Android 就會調用此函數。
那麼直接在SurfaceView中進行實現動畫的想法這裏沒有獲得很好的解決,而是我利用佈局的方式來一同顯示的方式,但願各位童鞋若是有好的方法,在SurfaceView中直接能使用動畫的建議和想法,但願留言給我,你們一塊兒學習 討論,謝謝 下面給出項目源碼:
源碼下載地址: http://www.himigame.com/android-game/331.html
(歡迎各位童鞋訂閱本博客,由於咱的更新速度但是很快的~娃哈哈)