李华明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
(欢迎各位童鞋订阅本博客,由于咱的更新速度但是很快的~娃哈哈)