在Android游戲開發4中講解了使用一張13幀的png圖片,采用設置可視區域的方式來實現動畫效果,但這是我們手動實現的,實際上Android提供了兩種自定義動畫的方式:Frame By Frame幀動畫和Tween Animation 漸變動畫。下面分別介紹:
第一類:Frame By Frame幀動畫(不推薦在游戲開發中使用)
所謂幀動畫,就是順序播放事先做好的圖像,類似於放電影。
分析: 此種方式類似我之前的那種利用設置可視區域的方式來實現動畫效果,不僅類似而且還不如!所以此種方式在此不予分析。
第二類:Tween Animation漸變動畫
即通過對對象不斷做圖像變換(平移、縮放、旋轉)產生動畫效果!實現方式其實就是預先定義一組指令,這些指令指定了圖形變換的類型、觸發時間、持續時間。這些指令可以是以 XML 文件方式定義,也可以是以源代碼方式定義。程序沿著時間線執行這些指令就可以實現動畫效果。
總結:在Android 游戲開發中我們優先選用兩種方式:第一種設置可視區域的方式來實現動畫效果(幀動畫),需要童鞋們手動實現,第二種就是Tween Animation。本文主要為大家講解Tween Animation。
在講述SurfaceView添加動畫之前,我們先來看看在View中如何實現Tween Animation以及Tween 中的四種效果。
MyViewAnimation.java
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
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游戲開發6和Android游戲開發7中都有了詳細講解,那麼在這裡),當然一並顯示也會有問題,比如我們存在了view和Surfaceiew,那麼按鍵的時候觸發的哪個?或者說如何去控制這兩個View?放心,我下面就跟大家一一來講解!
下面先讓我們把我們的view 和 Surfaceview 先同時顯示出來:【黑色的是MyView (View),白色是MySurfaceView(SurfaceView)】
先上張運行截圖: (圖4)
main.xml中的代碼:
XML/HTML代碼
- <?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中的代碼:
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:
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中直接使用動畫的方法,歡迎留言討論。