上次写了一个android模拟下雪的demo。
基本是模拟出来雪花飘落的效果了,但是并没有达到真正的“下雪”。因为一次只产生了几朵雪花而已。于是想改进一下。
我原打算改进的方法是:
定义一个雪花类,继承runnable,只要在控制类里面启动,就让雪花自动飘落,当落到屏幕下方的时候,重新回到屏幕顶部,然后再次下落……
这样设计,是因为我原先做过一下SWT的游戏,让物品自己绘制自己,自己控制自己的生命周期,这应该是面向对象设计的第一反应……
但是遇到了问题,因为在android中,界面显示是靠View来控制的,如果让需要View把自己的canvas传递给雪花,这样还不如让所有的界面显示都交给VIEW来处理。
在不停的实验之后,发现我自己的设计,我没有走通……归结原因,还是自己对android 2D方面的东西不了解。于是网上下载一些资料学习
-----------------------------------分割线,一下内容来源互联网---------------------------------------------------------
android中涉及到显示的基本都要交给View处理,这里就不不得不提到两个类:android.view.View和android.view.SurfaceView。
SurfaceView 是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView。
surfaceView 到底有哪些优势呢?
SurfaceView 可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder类,使用getHolder方法获取,相关的有
Canvas lockCanvas() ,Canvas lockCanvas(Rect dirty) 、void removeCallback(SurfaceHolder.Callback callback)、void unlockCanvasAndPost(Canvas canvas) 控制图形以及绘制,而在SurfaceHolder.Callback 接口回调中可以通过重写下面方法实现:
使用的SurfaceView的时候,一般情况下要对其进行创建,销毁,改变时的情况进行监视,这就要用到 SurfaceHolder.Callback.
class XxxView extends SurfaceView implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//看其名知其义,在surface的大小发生改变时激发
public void surfaceCreated(SurfaceHolder holder){}
//同上,在创建时激发,一般在这里调用画图的线程。
public void surfaceDestroyed(SurfaceHolder holder) {}
//同上,销毁时激发,一般在这里将画图的线程停止、释放。
}
对于Surface相关的,Android底层还提供了GPU加速功能,所以一般实时性很强的应用中主要使用SurfaceView而不是直接从View构建,同时后来做android 3d OpenGL中的GLSurfaceView也是从该类实现。
SurfaceView 和View 最本质的区别在于,surfaceView 是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。
那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。
所以基于以上,根据游戏特点,一般分成两类。
1 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。
3.Android中的SurfaceView类就是双缓冲机制。因此,开发游戏时尽量使用SurfaceView而不要使用View,这样的话效率较高,而且SurfaceView的功能也更加完善。
---------------------------------------------分割线---------------------------------------------
于是就写了一个简单的demo,让一个物体(我用的是一朵花),在屏幕中不停的移动。选择使用surfaceView
@Override
public void surfaceCreated(SurfaceHolder holder) {
SH = this.getHeight();
SW = this.getWidth();
th.start();
}
这里我们重载,surfaceCrate方法,获得屏幕的高度和宽度,启动绘制线程……因为只有当执行surfaceCreate的时候,界面才真正绘制出来,如果在构造函数中获得屏幕的高度和宽度,就只能为0;
public void draw() {
canvas = sfh.lockCanvas();
if (canvas == null) {// 防止在按返回键的时候,报空指针异常
return;
}
canvas.drawRect(0, 0, SW, SH, p); // 刷屏
canvas.save();
canvas.drawBitmap(bmp, bmp_x, bmp_y, p);
Log.v("DEBUG", bmp_x + " |" + bmp_y + " | " + speed_x + " |" + speed_y);
canvas.restore();
sfh.unlockCanvasAndPost(canvas);
}
这个方法会被线程不停的调用,让物体有移动的效果。其中
canvas.drawRect(0, 0, SW, SH, p); // 刷屏
负责清理上一次绘制留下的图片,让物体“动起来”。
为了防止当用户点击放回按钮的时候,线程仍在绘制,会报空指针异常,所以每次绘制前,要检查canvas是否为空。
private void path() {
if (bmp_x >= SW - 20) {
speed_x += 1;
if(speed_x>15){
speed_x = 3;
}
speed_x = -speed_x;
} else if (bmp_x <= 20) {
speed_x -=1;
if(speed_x<-15){
speed_x = -3;
}
speed_x = -speed_x;
}
if (bmp_y >= SH - 20) {
speed_y+=5;
if(speed_y>15){
speed_y = 3;
}
speed_y = -speed_y;
} else if (bmp_y <= 10){
speed_y -=5;
if(speed_y<-15){
speed_y = -3;
}
speed_y = -speed_y;
}
bmp_x += speed_x;
bmp_y += speed_y;
}
path这个方法控制着物体移动的速度,方向……
代码下载地址:点击打开链接