【Android】Android SurfaceView使用详解

http://codingnow.cn/android/603.html

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,源码如下:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.InputStream;public class GameSurfaceView 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.pic1, 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 blocke.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);}}



GameView.java中包含了一个绘图线程DrawThread,在线程的run方法中锁定Canvas、绘制精灵、更新精灵位置、释放Canvas等操作。因为精灵素材是一张大图,所以这里进行了裁剪生成一个二维数组。使用这个二维数组初始化了精灵四个方向的动画,下面看Sprite.java的源码。

import android.graphics.Bitmap;
import android.graphics.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);}}
}


精灵类主要是根据当前位置判断行走的方向,然后根据行走的方向更新精灵的位置,再绘制自身的动画。由于精灵的动画是一帧一帧的播放图片,所以这里封装了FrameAnimation.java,源码如下:

import android.graphics.Bitmap;public class FrameAnimation {/*** 动画显示的需要的资源*/private Bitmap[] bitmaps;/*** 动画每帧显示的时间*/private int[] duration;/*** 动画上一帧显示的时间*/protected Long lastBitmapTime;/*** 动画显示的索引值,防止数组越界*/protected int step;/*** 动画是否重复播放*/protected boolean repeat;/*** 动画重复播放的次数*/protected int repeatCount;/***     * @param bitmap:显示的图片
*     * @param duration:图片显示的时间
*     * @param repeat:是否重复动画过程
*     */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}}


FrameAnimation根据每一帧的显示时间返回当前的图片帧,若没有超过指定的时间则继续返回当前帧,否则返回下一帧。
接下来需要做的是让Activty显示的View为我们之前创建的GameView,然后设置全屏显示。

        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工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。



本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部