Android SurfaceView使用詳解(很好的實戰例子)
一、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) {
// 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);
}
}
GameView.java中包含了一個繪圖執行緒DrawThread,線上程的run方法中鎖定Canvas、繪製精靈、更新精靈位置、釋放Canvas等操作。因為精靈素材是一張大圖,所以這裡進行了裁剪生成一個二維陣列。使用這個二維陣列初始化了精靈四個方向的動畫,下面看Sprite.java的原始碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
精靈類主要是根據當前位置判斷行走的方向,然後根據行走的方向更新精靈的位置,再繪製自身的動畫。由於精靈的動畫是一幀一幀的播放圖片,所以這裡封裝了FrameAnimation.java,原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
FrameAnimation根據每一幀的顯示時間返回當前的圖片幀,若沒有超過指定的時間則繼續返回當前幀,否則返回下一幀。
接下來需要做的是讓Activty顯示的View為我們之前建立的GameView,然後設定全屏顯示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
現在執行Android工程,應該就可以看到一個手持寶劍的武士在沿著螢幕不停的走了。