在android中开发游戏,一般来说,或想写一个复杂一点的游戏,是必须用到SurfaceView来开发的。
经过这一阵子对android的学习,我找到了自已在android中游戏开发的误区,不要老想着用Layout和view去实现,不要将某个游戏
中的对象做成一个组件来处理。应该尽量想着在Canvas(画布)中画出游戏戏中的背景、人物、动画等...
SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用
窗体部件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView,Button)
要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图操作,如画点画线。
还要注意的是,使用它的时候,一般都是出现在最顶层的:The view hierarchy will take care ofcorrectly compositing
with the Surface any siblings of the SurfaceView that wouldnormally appear on top of it.
使用的SurfaceView的时候,一般情况下还要对其进行创建,销毁,改变时的情况进行监视,这就要用到SurfaceHolder.Callback.
class BBatt extends SurfaceView implements SurfaceHolder.Callback{
public void surfaceChanged(SurfaceHolder holder,int format,intwidth,int height){}
//看其名知其义,在surface的大小发生改变时激发
public void surfaceCreated(SurfaceHolder holder){}
//同上,在创建时激发,一般在这里调用画图的线程。
public void surfaceDestroyed(SurfaceHolder holder) {}
//同上,销毁时激发,一般在这里将画图的线程停止、释放。
}
例子:
public class BBatt extends SurfaceView implementsSurfaceHolder.Callback, OnKeyListener {
privateBFairy bFairy;
privateDrawThread drawThread;
publicBBatt(Context context) {
super(context);
this.setLayoutParams(newwGroup.LayoutParams(Global.battlefieldWidth,Global.battlefieldHeight));
this.getHolder().addCallback( this );
this.setFocusable( true );
this.setOnKeyListener( this );
bFairy = new BFairy(this.getContext());
}
publicvoid surfaceChanged(SurfaceHolder holder,
intformat,int width,int height) {
drawThread = new DrawThread(holder);
drawThread.start();
}
public voidsurfaceD estroyed(SurfaceHolder holder) {
if( drawThread != null ) {
drawThread.doStop();
while(true) try {
drawThread.join();
break ;
} catch(Exception ex) {}
}
}
public boolean onKey(View view, int keyCode, KeyEvent event){}
}
实例2:用线程画一个蓝色的长方形。
package com.g3.test;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Test extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
//内部类
class MyView extends SurfaceView implementsSurfaceHolder.Callback{
SurfaceHolder holder;
public MyView(Context context) {
super(context);
holder = this.getHolder();//获取holder
holder.addCallback(this);
//setFocusable(true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, intwidth,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
new Thread(new MyThread()).start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
//内部类的内部类
class MyThread implements Runnable{
@Override
public void run() {
Canvas canvas = holder.lockCanvas(null);//获取画布
Paint mPaint = new Paint();
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(40,60,80,80), mPaint);
holder.unlockCanvasAndPost(canvas);//解锁画布,提交画好的图像
}
}
}
}
访问SurfaceView的底层图形是通过SurfaceHolder接口来实现的,通过getHolder()方法可以得到这个SurfaceHolder对象。你应该实现surfaceCreated(SurfaceHolder)和surfaceDestroyed(SurfaceHolder)方法来知道在这个Surface在窗口的显示和隐藏过程中是什么时候创建和销毁的。
SurfaceView可以在多线程中被访问。
注意:一个SurfaceView只在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()调用之间是可用的,其他时间是得不到它的Canvas对象的(null)。
我的访问过程:
创建一个SurfaceView的子类,实现SurfaceHolder.Callback接口。
得到这个SurfaceView的SurfaceHolder对象holder。
holder.addCallback(callback),也就是实现SurfaceHolder.Callback接口的类对象。
在SurfaceHolder.Callback.surfaceCreated()调用过后holder.lockCanvas()对象就可以得到SurfaceView对象对应的Canvas对象canvas了。
用canvas对象画图。
画图结束后调用holder.unlockCanvasAndPost()就把图画在窗口中了。
SurfaceView可以多线程访问,在多线程中画图。
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private Context mContext;
private SurfaceHolder mHolder;
public TouchScreenAdjusterSurfaceView(Context context,) {
super(context);
mContext = context;
mHolder = TouchScreenAdjusterSurfaceView.this.getHolder();
mHolder.addCallback(TouchScreenAdjusterSurfaceView.this);
this.setFocusableInTouchMode(true); // to make sure that we canget
// touch events and key events,and
// "setFocusable()" to make sure we
// can get key events
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, intwidth,
int height) {
// TODO Auto-generated method stub
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//now you can get the Canvas and draw something here
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
public void drawMyShape(PointPostion ps) {
mCanvas = mHolder.lockCanvas();
// draw anything you like
mHolder.unlockCanvasAndPost(mCanvas);
}
}**************************************************************************************************当我们需要开发一个复杂的游戏的时候,而且对程序的执行效率要求很高时,View类就不能满足需求了,这时必须用SurfaceView类进行开发。例如,对速度要求很高的游戏时,View类就不能满足需求了,这时必须使用SurfaceView类进行开发。例如,对速度要求很高的游戏,可以使用双缓冲来显示。游戏中的背景、人物、动画等都需要绘制在一个画布(Canvas)上,而SurfaceView可以直接访问一个画布,SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用使用的。每个Surface创建一个Canvas对象(但属性时常改变),用来管理View和Surface上的绘图操作。
在使用SurfaceView开发时需要注意的是,使用它绘图时,一般都是出现在最顶层。使用时还需要对其进行创建、销毁、情况改变时进行监视,这就要实现SurfaceHolder.Callback接口,如果要对被绘制的画布进行裁剪,控制其大小都需要使用SurfaceHolder来完成处理。在程序中,SurfaceHolder对象需要通过getHolder方法来获得,同时还需要addCallback方法来添加 "回调函数"
在一般的情况下,应用程序的View都是在相同的GUI线程中绘制的。这个主应用程序线程同时也用来处理所有的用户交互(例如,按钮单击或者文本输入)。
当需要快速地更新View的UI,或者当渲染代码阻塞GUI线程的时间过长的时候,SurfaceView就是解决上述问题的最佳选择。SurfaceView封装了一个Surface对象,而不是Canvas。这一点很重要,因为Surface可以使用后台线程绘制。对于那些资源敏感的操作,或者那些要求快速更新或者高速帧率的地方,例如,使用3D图形,创建游戏,或者实时预览摄像头,这一点特别有用。
1. 何时应该使用SurfaceView?
SurfaceView使用的方式与任何View所派生的类都是完全相同的。可以像其他View那样应用动画,并把它们放到布局中。
SurfaceView封装的Surface支持使用本章前面所描述的所有标准Canvas方法进行绘图,同时也支持完全的OpenGLES库。
使用OpenGL,你可以再Surface上绘制任何支持的2D或者3D对象,与在2D画布上模拟相同的效果相比,这种方法可以依靠硬件加速(可用的时候)来极大地提高性能。
对于显示动态的3D图像来说,例如,那些使用GoogleEarth功能的应用程序,或者那些提供沉浸体验的交互式游戏,SurfaceView特别有用。它还是实时显示摄像头预览的最佳选择。
2. 创建一个新的SurfaceView控件
要创建一个新的SurfaceView,需要创建一个新的扩展了SurfaceView的类,并实现SurfaceHolder.Callback。
SurfaceHolder回调可以在底层的Surface被创建和销毁的时候通知View,并传递给它对SurfaceHolder对象的引用,其中包含了当前有效的Surface。
一个典型的SurfaceView设计模型包括一个由Thread所派生的类,它可以接收对当前的SurfaceHolder的引用,并独立地更新它。
3. SurfaceHolder.Callback 回调接口的方法介绍
surfaceChanged: 在Surface的大小发生改变时激发。
surfaceCreated: 在创建Surface时激发
surfaceDestroyed: 在销毁Surface时激发
addCallback: 给SurfaceView添加一个回调函数
lockCanvas: 锁定画布,绘图之前必须锁定画布才能得到当前画布对象。
unlockCanvasAndPost: 开始绘图时锁定了画布,绘制完成之后解锁画布。
removeCallback: 从SurfaceView 中移除回调函数。
SurfaceView 和 View的明显不同之处在于,继承SurfaceView 的视图可以另起一个线程或者 说在子线程中 更新视图 这与我上一篇的另外一个示例android 自定义View类的简单使用 示例 (这个示例请点这里http://www.eoeandroid.com/thread-72273-1-1.html)明显的一个区别就是SurfaceView 的画图方法 是在 子线程中执行的 而 View类的那个示例 的画图方法 是在UI线程中执行的。其实SurfaceView按道理应该违背了 android的 单线程模型 因为它在子线称中 画图 刷新界面 来更新UI 至于它为什么可以这么做 我也不知道 有时间研究一下吧 可能它是一种特殊的视图吧。 还有要注意的一点就是 我们在绘图之前必须使用lockCanvas 方法锁定画布,并得到画布,然后再画布上绘制;当绘制完成后,使用unlockCanvasAndPost方法解锁画布,然后就显示到屏幕上。SurfaceView 类的事件处理规则和View一样。
下面是这样一个例子 这个例子实现了一个不断变换颜色的圆形 并且实现了SurfaceView的事件处理。 我们可以通过模拟器的 上下键来调节 这个圆 在屏幕中的位置。下面先看一下运行效果吧。
效果图:
Java代码:
- package eoe.Demo;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.view.SurfaceHolder;
- import android.view.SurfaceView;
- public class GameSurfaceView extends SurfaceView implementsSurfaceHolder.Callback,Runnable{
- //控制循环
- boolean mbLoop = false;
- //定义SurfaceHolder对象
- SurfaceHolder mSurfaceHolder = null;
- int miCount = 0;
- int y =50;
- public GameSurfaceView(Context context) {
- super(context);
- //实例化SurfaceHolder
- mSurfaceHolder = this.getHolder();
- //添加回调 函数
- //注意这里这句 mSurfaceHolder.addCallback(this)这句执行完了之后
- //马上就会回调 surfaceCreated方法了 然后开启线程 执行绘图方法这里这个执行顺序要搞清楚
- mSurfaceHolder.addCallback(this);
- this.setFocusable(true);
- mbLoop = true;
- }
- //在surface的大小发生改变时激发
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format,int width,
- int height) {
- }
- //surface创建时激发 此方法在主线程总执行
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- //开启绘图线程
- new Thread(this).start();
- }
- //在surface销毁时激发
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- //停止循环
- mbLoop = false;
- }
- //绘图循环
- @Override
- public void run() {
- while (mbLoop){
- try {
- Thread.sleep(200);
- } catch (Exception e) {
- }
- //至于这里为什么同步这就像一块画布 你不能让两个人同时往上边画画
- synchronized( mSurfaceHolder ){
- Draw();
- }
- }
- }
- //绘图方法 注意这里是另起一个线程来执行绘图方法了不是在UI 线程了
- public void Draw(){
- //锁定画布,得到canvas 用SurfaceHolder对象的lockCanvas方法
- Canvas canvas = mSurfaceHolder.lockCanvas();
- if (mSurfaceHolder==null || canvas == null) {
- return;
- }
- if (miCount < 100) {
- miCount++;
- }else {
- miCount = 0;
- }
- //绘图
- Paint mPaint = new Paint();
- //给Paint对象加上抗锯齿标志
- //http://tech.techweb.com.cn/thread-459611-1-1.html 详细说明可以看看这里--->大家直接复制链接到 浏览器打开吧。
- mPaint.setAntiAlias(true);
- mPaint.setColor(Color.BLACK);
- //绘制矩形---清屏作用
- canvas.drawRect(0, 0, 320, 480, mPaint);
- switch (miCount % 4) {
- case 0:
- mPaint.setColor(Color.BLUE);
- break;
- case 1:
- mPaint.setColor(Color.GREEN);
- break;
- case 2:
- mPaint.setColor(Color.RED);
- case 3:
- mPaint.setColor(Color.YELLOW);
- default:
- mPaint.setColor(Color.WHITE);
- break;
- }
- //绘制矩形
- canvas.drawCircle((320 - 25) / 2, y, 50, mPaint);
- //绘制后解锁,绘制后必须解锁才能显示
- mSurfaceHolder.unlockCanvasAndPost(canvas);
- }
- }
*************************************************************************************************
我们还是接上一篇的代码来讲解:
Java代码:
- package eoe.Demo;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.KeyEvent;
- import android.view.MotionEvent;
- public class Activity01 extends Activity {
- GameSurfaceView mGameSurfaceView;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //创建GameSurfaceView对象
- mGameSurfaceView = new GameSurfaceView(this);
- //设置显示GameSurfaceView视图
- setContentView(mGameSurfaceView);
- }
- //触笔事件 返回值为true 父视图不做处理以下返回值为true的都是不做处理的
- public boolean onTouchEvent(MotionEvent event){
- return true;
- }
- //按键按下事件
- public boolean onKeyDown(int keyCode, KeyEventevent){
- return true;
- }
- //按键弹起事件
- public boolean onKeyUp(int keyCode, KeyEvent event){
- switch (keyCode) {
- //上方向键
- case KeyEvent.KEYCODE_DPAD_UP:
- mGameSurfaceView.y-=3;
- break;
- //下方向键
- case KeyEvent.KEYCODE_DPAD_DOWN:
- mGameSurfaceView.y+=3;
- break;
- }
- return false;
- }
- public boolean onKeyMultiple(int keyCode, int repeatCount,KeyEvent event){
- return true;
- }
- }
写完这个例子 我有些疑问
可不可以 在 类似这种 SurfaceView 上边放一些控件呢?
前边已经说了Surface是提供给需要直接画像素而不是使用窗体部件的应用使用的。 但是 我还是想知道 为什么不能再Surface上边放 一个控件 比如 一个 button 或者 一个 textview 这里我们来拿button 举例子比如我们想在 Surface上边放一个 button 这时 我们就需要 new一个 button 按钮了呗 在Acvity01类中我们可以这样做 Button button = new Button(this);
但是 你new 完这个 button 你怎么把它 放倒 SurfaceView 这个视图上呢?因为在Activity01这个类中我们是这样设置布局的 setContentView(mGameSurfaceView);它的布局就是一个 SurfaceView 说白了也是一个View 这就相当于在一个视图上在放一个视图
这是放不了的。
这时候有人就想了 我们可以把Button 放在 GameSurfaceView 类中 这个想法真是聪明 太聪明了。 然后我们就到GameSurfaceView类中 这样写 Button button = new Button(this); 你会惊奇的发现他报错了。 为什么呢? 下面我把 android 中 Button的源码贴上来大家看看
Java代码:
- @RemoteView
- public class Button extends TextView {
- public Button(Context context) {
- this(context, null);
- }
- public Button(Context context, AttributeSet attrs) {
- this(context, attrs,com.android.internal.R.attr.buttonStyle);
- }
- public Button(Context context, AttributeSet attrs, intdefStyle) {
- super(context, attrs, defStyle);
- }
- }
Button 这个类比较简单 就三个构造方法。 但是 每个 构造方法 都需要一个 Context 对象。
这一点大家显然都已经注意到了。 那为什么 我可以再 在继承Activity的类里边 new一个Button 而在 继承SurfaceView的类中确不行呢。
我们分别来看一下Activity类和GameSurfaceView类的继承关系
我想大家已经知道 为什么 SurfaceView上边不可以放一些我们常用的控件了 也知道为什么我们可以再Activity类 里边可以 new Button 而在SurfaceView 类里边却不行 因为我们的Activity类本身就是一个 Context对象。 而我们的SurfaceView本身 不是 一个Context对象。 而我们要new 一个Button怎么都得需要一个Context对象。从这个角度其实就可以解释为什么 我们不能再SurfaceView上边放一些我们常用的控件了Button TextView之类的。