1. SurfaceView的定义
前面已经介绍过View了,下面来简单介绍一下SurfaceView,参考SDK文档和网络资料:SurfaceView是View的子类,它内嵌了一个专门用于绘制的Surface,你可以控制这个Surface的格式和尺寸,Surfaceview控制这个Surface的绘制位置。surface是纵深排序(Z-ordered)的,说明它总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个可见区域内的surface内容才可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面有透明控件,那么每次surface变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。
SurfaceView默认使用双缓冲技术的,它支持在子线程中绘制图像,这样就不会阻塞主线程了,所以它更适合于游戏的开发。
2. SurfaceView的使用
首先继承SurfaceView,并实现SurfaceHolder.Callback接口,实现它的三个方法:surfaceCreated,surfaceChanged,surfaceDestroyed。
surfaceCreated(SurfaceHolder holder):surface创建的时候调用,一般在该方法中启动绘图的线程。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):surface尺寸发生改变的时候调用,如横竖屏切换。
surfaceDestroyed(SurfaceHolder holder) :surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。
还需要获得SurfaceHolder,并添加回调函数,这样这三个方法才会执行。
3. SurfaceView实战
下面通过一个小demo来学习SurfaceView的使用,绘制一个精灵,该精灵有四个方向的行走动画,让精灵沿着屏幕四周不停的行走。游戏中精灵素材和最终实现的效果图:
首先创建核心类GameView.java,源码如下:
public class GameView extends SurfaceView implements SurfaceHolder.Callback { //屏幕宽高 public static int SCREEN_WIDTH; public static int SCREEN_HEIGHT; private Context mContext; private SurfaceHolder mHolder; //最大帧数 (1000 / 30) private static final int DRAW_INTERVAL = 30; private DrawThread mDrawThread; private FrameAnimation []spriteAnimations; private Sprite mSprite; private int spriteWidth = 0; private int spriteHeight = 0; private float spriteSpeed = (float)((500 * SCREEN_WIDTH / 480) * 0.001); private int row = 4; private int col = 4; public GameSurfaceView(Context context) { super(context); this.mContext = context; mHolder = this.getHolder(); mHolder.addCallback(this); initResources(); mSprite = new Sprite(spriteAnimations,0,0,spriteWidth,spriteHeight,spriteSpeed); } private void initResources() { Bitmap[][] spriteImgs = generateBitmapArray(mContext, R.drawable.sprite, row, col); spriteAnimations = new FrameAnimation[row]; for(int i = 0; i < row; i ++) { Bitmap []spriteImg = spriteImgs[i]; FrameAnimation spriteAnimation = new FrameAnimation(spriteImg,new int[]{150,150,150,150},true); spriteAnimations[i] = spriteAnimation; } } public Bitmap decodeBitmapFromRes(Context context, int resourseId) { BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; InputStream is = context.getResources().openRawResource(resourseId); return BitmapFactory.decodeStream(is, null, opt); } public Bitmap createBitmap(Context context, Bitmap source, int row, int col, int rowTotal, int colTotal) { Bitmap bitmap = Bitmap.createBitmap(source, (col - 1) * source.getWidth() / colTotal, (row - 1) * source.getHeight() / rowTotal, source.getWidth() / colTotal, source.getHeight() / rowTotal); return bitmap; } public Bitmap[][] generateBitmapArray(Context context, int resourseId, int row, int col) { Bitmap bitmaps[][] = new Bitmap[row][col]; Bitmap source = decodeBitmapFromRes(context, resourseId); this.spriteWidth = source.getWidth() / col; this.spriteHeight = source.getHeight() / row; for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { bitmaps[i - 1][j - 1] = createBitmap(context, source, i, j, row, col); } } if (source != null && !source.isRecycled()) { source.recycle(); source = null; } return bitmaps; } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } public void surfaceCreated(SurfaceHolder holder) { if(null == mDrawThread) { mDrawThread = new DrawThread(); mDrawThread.start(); } } public void surfaceDestroyed(SurfaceHolder holder) { if(null != mDrawThread) { mDrawThread.stopThread(); } } private class DrawThread extends Thread { public boolean isRunning = false; public DrawThread() { isRunning = true; } public void stopThread() { isRunning = false; boolean workIsNotFinish = true; while (workIsNotFinish) { try { this.join();// 保证run方法执行完毕 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } workIsNotFinish = false; } } public void run() { long deltaTime = 0; long tickTime = 0; tickTime = System.currentTimeMillis(); while (isRunning) { Canvas canvas = null; try { synchronized (mHolder) { canvas = mHolder.lockCanvas(); //设置方向 mSprite.setDirection(); //更新精灵位置 mSprite.updatePosition(deltaTime); drawSprite(canvas); } } catch (Exception e) { e.printStackTrace(); } finally { if (null != mHolder) { mHolder.unlockCanvasAndPost(canvas); } } deltaTime = System.currentTimeMillis() - tickTime; if(deltaTime < DRAW_INTERVAL) { try { Thread.sleep(DRAW_INTERVAL - deltaTime); } catch (InterruptedException e) { e.printStackTrace(); } } tickTime = System.currentTimeMillis(); } } } private void drawSprite(Canvas canvas) { //清屏操作 canvas.drawColor(Color.BLACK); mSprite.draw(canvas); } }
public class Sprite { public static final int DOWN = 0; public static final int LEFT = 1; public static final int RIGHT = 2; public static final int UP = 3; public float x; public float y; public int width; public int height; //精灵行走速度 public double speed; //精灵当前行走方向 public int direction; //精灵四个方向的动画 public FrameAnimation[] frameAnimations; public Sprite(FrameAnimation[] frameAnimations, int positionX, int positionY, int width, int height, float speed) { this.frameAnimations = frameAnimations; this.x = positionX; this.y = positionY; this.width = width; this.height = height; this.speed = speed; } public void updatePosition(long deltaTime) { switch (direction) { case LEFT: //让物体的移动速度不受机器性能的影响,每帧精灵需要移动的距离为:移动速度*时间间隔 this.x = this.x - (float) (this.speed * deltaTime); break; case DOWN: this.y = this.y + (float) (this.speed * deltaTime); break; case RIGHT: this.x = this.x + (float) (this.speed * deltaTime); break; case UP: this.y = this.y - (float) (this.speed * deltaTime); break; } } /** * 根据精灵的当前位置判断是否改变行走方向 */ public void setDirection() { if (this.x <= 0 && (this.y + this.height) < GameSurfaceView.SCREEN_HEIGHT) { if (this.x < 0) this.x = 0; this.direction = Sprite.DOWN; } else if ((this.y + this.height) >= GameSurfaceView.SCREEN_HEIGHT && (this.x + this.width) < GameSurfaceView.SCREEN_WIDTH) { if ((this.y + this.height) > GameSurfaceView.SCREEN_HEIGHT) this.y = GameSurfaceView.SCREEN_HEIGHT - this.height; this.direction = Sprite.RIGHT; } else if ((this.x + this.width) >= GameSurfaceView.SCREEN_WIDTH && this.y > 0) { if ((this.x + this.width) > GameSurfaceView.SCREEN_WIDTH) this.x = GameSurfaceView.SCREEN_WIDTH - this.width; this.direction = Sprite.UP; } else { if (this.y < 0) this.y = 0; this.direction = Sprite.LEFT; } } public void draw(Canvas canvas) { FrameAnimation frameAnimation = frameAnimations[this.direction]; Bitmap bitmap = frameAnimation.nextFrame(); if (null != bitmap) { canvas.drawBitmap(bitmap, x, y, null); } } }
public class FrameAnimation{ /**动画显示的需要的资源 */ private Bitmap[] bitmaps; /**动画每帧显示的时间 */ private int[] duration; /**动画上一帧显示的时间 */ protected Long lastBitmapTime; /**动画显示的索引值,防止数组越界 */ protected int step; /**动画是否重复播放 */ protected boolean repeat; /**动画重复播放的次数*/ protected int repeatCount; /** * @param bitmap:显示的图片<br/> * @param duration:图片显示的时间<br/> * @param repeat:是否重复动画过程<br/> */ public FrameAnimation(Bitmap[] bitmaps, int duration[], boolean repeat) { this.bitmaps = bitmaps; this.duration = duration; this.repeat = repeat; lastBitmapTime = null; step = 0; } public Bitmap nextFrame() { // 判断step是否越界 if (step >= bitmaps.length) { //如果不无限循环 if( !repeat ) { return null; } else { lastBitmapTime = null; } } if (null == lastBitmapTime) { // 第一次执行 lastBitmapTime = System.currentTimeMillis(); return bitmaps[step = 0]; } // 第X次执行 long nowTime = System.currentTimeMillis(); if (nowTime - lastBitmapTime <= duration[step]) { // 如果还在duration的时间段内,则继续返回当前Bitmap // 如果duration的值小于0,则表明永远不失效,一般用于背景 return bitmaps[step]; } lastBitmapTime = nowTime; return bitmaps[step++];// 返回下一Bitmap } }
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); DisplayMetrics outMetrics = new DisplayMetrics(); this.getWindowManager().getDefaultDisplay().getMetrics(outMetrics); GameSurfaceView.SCREEN_WIDTH = outMetrics.widthPixels; GameSurfaceView.SCREEN_HEIGHT = outMetrics.heightPixels; GameSurfaceView gameView = new GameSurfaceView(this); setContentView(gameView); }
现在运行Android工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。
转载请注明来自:Alex Zhou,本文链接:http://codingnow.cn/android/603.html