SurfaceView
是游戏中最常用的组件,但是SurfaceView
的动态绘制会造成闪屏、黑屏的现象,利用双缓冲可以缓解这一现象,并且这里对双缓冲就行了二次优化处理,并且Handler与其他线程的通信方式
1、普通双缓冲机制的实现
这里仅截取实现的一部分
// 游戏界面
public class GameView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
private Canvas canvas; //画布
private Paint paint; //画笔
private SurfaceHolder surfaceHolder;// 控制容器
public SurfaceHolder getSurfaceHolder() {
return surfaceHolder;
}
public boolean gameRunFlag; // 子线程标志位
private Bitmap bitmapbuffer;// '假屏幕'的'截图',缓冲下屏幕 用来画在假屏幕上,利用双缓冲,减少绘制的时候屏幕闪烁现象
private Canvas canvasbuffer;//假的屏幕(画布) ,利用双缓冲,减少绘制的时候屏幕闪烁现象
private Context context; // 绘制到的目标容器
private static GameView gameView; //动画绘制'管家',总管所有的屏幕绘制动画,可通过管家向页面提交控制的事件及结果
//与其他线程通信
private Handler handler = new Handler();
//将背景的静止元素只绘画一次,下次直接拿去用即可
private Canvas canvasback;
private Bitmap bitmapback;
//覆盖默认构造方法,在指定容器运行
public GameView(Context context) {
super(context);
this.context=context;
paint=new Paint();
surfaceHolder=getHolder();
gameRunFlag=true;
gameView=this;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 屏幕缓冲 *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//假屏幕的图片(类似屏幕截图了),缓冲下屏幕,用来画在假屏幕上
bitmapbuffer= Bitmap.createBitmap(Config.deviceWidth,Config.deviceHeight,Bitmap.Config.ARGB_8888);
//假的屏幕(就是在一个画布上画了假屏幕的图片),并且把假屏幕的内容缓冲到 假屏幕图片上
canvasbuffer=new Canvas(bitmapbuffer);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 游戏背景 *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bitmapback= Bitmap.createBitmap(Config.deviceWidth,Config.deviceHeight,Bitmap.Config.ARGB_8888);
canvasback=new Canvas(bitmapback);
//surfaceview 进行而外的操作用的线程
handler= new Handler();
//getHolder().addCallback(this);
surfaceHolder.addCallback(this);
//surfaceCreated(surfaceHolder);
//Log.i("游戏界面","标志位="+gameRunFlag);
}
双缓冲实现及优化
在普通的双缓冲中我们是将屏幕全部重新绘制一遍的,但是其实我们改变的只是一部分,而对于一些相对来说静止的如背景动画、工具栏等其实是不会改变的,我们可以把这一部分剥离出来,只绘制一次,这样就减少了绘制量,提高绘制的效率,实现如下:
//线程
@Override
public void run() {
//Log.i("游戏界面","run="+gameRunFlag);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 游戏背景 只画一次静止的背景元素 *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//画背景
canvasback.drawBitmap(Config.gameImg_back,0,0,paint);
//画工具栏
canvasback.drawBitmap(Config.border_back,0,(Config.deviceHeight-(Config.deviceHeight/7)),paint);
//画杀敌图标
canvasback.drawBitmap(Config.bate_logo,Config.deviceWidth-(Config.deviceWidth-Config.NenegLiang_Context_X)*2+Config.deviceWidth/192, (float) (Config.deviceHeight/8*6.9),paint);
//画退出按钮
canvasback.drawBitmap(Config.gema_over,Config.game_over_X ,Config.game_over_Y,paint);
/*
动态元素绘制
*/
while(gameRunFlag){
synchronized (surfaceHolder){//线程同步锁
try{
//锁住画布
canvas=surfaceHolder.lockCanvas();
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 屏幕缓冲,把内容先缓冲到假屏幕里 *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//清空屏幕 画背景
canvasbuffer.drawBitmap(bitmapback,0,0,null);
//画能量图标 和 能量值
paint.setColor(Color.rgb(0 ,255 ,255));
paint.setTextSize(Config.deviceHeight/18);
canvasbuffer.drawText("+"+Config.L_number,Config.NenegLiang_Context_X+Config.neng_logo[0].getWidth(),Config.NenegLiang_Context_Y+Config.deviceHeight/108,paint);
canvasbuffer.drawBitmap(Config.neng_logo[this.neng_index],Config.NenegLiang_Context_X-Config.deviceWidth/192,(float) (Config.deviceHeight/8*6.9),paint);
if(System.currentTimeMillis()-this.neng_time>400){
this.neng_index=(this.neng_index+1)%Config.neng_logo.length;
this.neng_time=System.currentTimeMillis();
}
//画杀敌图标 和 击杀数
paint.setColor(Color.rgb(255 ,0 ,0));
canvasbuffer.drawText("+"+Config.SCORE,Config.deviceWidth-(Config.deviceWidth-Config.NenegLiang_Context_X)*2+Config.bate_logo.getWidth()+Config.deviceWidth/192,Config.NenegLiang_Context_Y+Config.deviceHeight/108,paint);
//画笔还原
paint.setColor(color);
updateData();//调用更新数据
ondraw(canvasbuffer);//更新数据后,再绘制容器的全部内容
// canvasbuffer.setBitmap(bitmapbuffer);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 把缓冲的假屏幕,画到真正的屏幕里面 *
* (就行照镜子一样的照上去) *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
canvas.drawBitmap(bitmapbuffer,0,0,null);
}catch(Exception e){
e.getMessage();
}finally{
//开锁,画布可以被再次更新了
if(canvas!=null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
// gameRunFlag=false;
}
//锁外休眠 给其他线程留运行空间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2、Handler线程外通信
SurfaceView
中是一个自线程,依赖于另外的实现线程来启动,如果我们需要在屏幕上显示提示信息,绘制弹出对话框等,那么就需要使用Handler
来实现与外部的通信,实现如下:
这里仅截取一部分代码
//游戏结束 对话框
public void gameOver(){
handler.post(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(50);
//调用弹出对话框的依赖界面自生管理者
RunYXActivity.getRunActivity().gameOver();
// System.exit(0);
//surfaceDestroyed(surfaceHolder);
}catch(Exception e){
e.printStackTrace();
}
}
});
}
//显示提示信息
handler.post(new Runnable(){
@Override
public void run() {
Toast.makeText(context,name+"被杀!",Toast.LENGTH_LONG).show();
}
});
其实我们很容易发现Handler
也是启动来一个子线程来实现的通信