一、surfaceview 
在显示时才会调用callback中的surfaceCreated。注意,是在显示时,在初始化时不会调用
在隐藏时会调用callback中的surfaceDestroyed

二、清屏操作

public void clearDraw(SurfaceHolder holder,int color) {
    Log.w("tan","clearDraw");
    Canvas canvas = null;
    try {
        canvas = holder.lockCanvas(null);
        canvas.drawColor(color);
    }catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }finally {
        if(canvas != null) {
         holder.unlockCanvasAndPost(canvas); 
        }
    }
}

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) {
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);
}

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

 

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

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
}
 
}

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

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