Android SurfaceView 繪圖覆蓋刷新及臟矩形刷新方法

作者: 斯克迪亞  來源: 博客園  發布時間: 2010-11-08 14:27  閱讀: 17523 次  推薦: 5   原文鏈接   [收藏]  

  SurfaceView在Android中用作游戲開發是最適宜的,本文就將演示游戲開發中常用的兩種繪圖刷新策略在SurfaceView中的實現方法。

  首先我們來看一下本例需要用到的兩個素材圖片:

imageimage  bj.jpg就是一個漸變圖,用作背景。

  question.png是一個半透明的圖像,我們希望將它放在上面,圍繞其圓心不斷旋轉。

  實現代碼如下:

 
package SkyD.SurfaceViewTest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Main extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(
new MySurfaceView(this));
}


// 自定義的SurfaceView子類
class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

// 背景圖
private Bitmap BackgroundImage;
// 問號圖
private Bitmap QuestionImage;

SurfaceHolder Holder;


public MySurfaceView(Context context) {
super(context);
BackgroundImage
= BitmapFactory.decodeResource(getResources(),
R.drawable.bg);
QuestionImage
= BitmapFactory.decodeResource(getResources(),
R.drawable.question);

Holder
= this.getHolder();// 獲取holder
Holder.addCallback(this);
}

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub

}

@Override

public void surfaceCreated(SurfaceHolder holder) {
// 啟動自定義線程
new Thread(new MyThread()).start();
}

@Override

public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
// 自定義線程類
class MyThread implements Runnable {
@Override

public void run() {
Canvas canvas
= null;
int rotate = 0;// 旋轉角度變量
while (true) {
try {
canvas
= Holder.lockCanvas();// 獲取畫布
Paint mPaint = new Paint();
// 繪制背景
canvas.drawBitmap(BackgroundImage, 0, 0, mPaint);
// 創建矩陣以控制圖片旋轉和平移
Matrix m = new Matrix();
// 設置旋轉角度
m.postRotate((rotate += 48) % 360,
QuestionImage.getWidth()
/ 2,
QuestionImage.getHeight()
/ 2);
// 設置左邊距和上邊距
m.postTranslate(47, 47);
// 繪制問號圖
canvas.drawBitmap(QuestionImage, m, mPaint);
// 休眠以控制最大幀頻為每秒約30幀
Thread.sleep(33);
}
catch (Exception e) {
}
finally {
Holder.unlockCanvasAndPost(canvas);
// 解鎖畫布,提交畫好的圖像
}
}
}
}
}
}

  模擬器中的運行效果:

image  (注:圖中的問號圖形是在不斷旋轉中的)

  這看起來不錯,但是有一個問題:我們在代碼中設置的幀頻最大值是每秒30幀,而實際運行時的幀頻根據目測就能看出是到不了30幀的,這是因為程序在每一幀都要對整個畫面進行重繪,過多的時間都被用作繪圖處理,所以難以達到最大幀頻。

  臟矩形刷新

  接下來我們將采取臟矩形刷新的方法來優化性能,所謂臟矩形刷新,意為僅刷新有新變化的部分所在的矩形區域,而其他沒用的部分就不去刷新,以此來減少資源浪費。

  我們可以通過在獲取Canvas畫布時,為其指派一個參數來聲明我們需要畫布哪個局部,這樣就可以只獲得這個部分的控制權:

image  在這里為了便于觀察,我將矩形區域設定為問號圖形的1/4區域,也就是說在整個畫面中我們僅僅更新問號圖形的1/4大小那么點區域,其執行效果為:

SNAGHTML342f602  可以看到,僅有那1/4區域在快速刷新,其他部分都是靜止不動的了,現在的刷新幀頻差不多已經能達到最大幀頻了,我們的優化起作用了:)

  不過別高興的太早,實際上如果把刷新區域擴大到整個問號圖形所在的矩形區域的話,你會發現優化作用變得微乎其微了,還是沒法達到最大幀頻的,因為更新區域增大了3倍,帶來的資源消耗也就大幅增加。

  覆蓋刷新

  這種情況下就應當考慮結合覆蓋刷新方法再進一步優化了。

  試想一下,我們每次刷新時最大的消耗在哪?

  沒錯,在背景圖繪制上,這個繪制區域非常大,會消耗我們很多資源,但實際上背景圖在此例中是從不變化的,也就是說我們浪費了很多資源在無用的地方。

  那么可不可以只繪制一次背景,以后每次都只繪制會動的問號圖形呢?

  完全可以,嘗試修改一下代碼,再前面加一個幀計數器,然后我們僅在第一幀的時候繪制背景:

image  這樣很簡單,但是改后直接運行的話你會發現一個奇怪的狀況:

image  問號圖案會變得有殘影了。

  啊哈,這正是我使用半透明圖案做范例的目的,通過這個重影,我們就能看出,覆蓋刷新其實就是將每次的新的圖形繪制到上一幀去,所以如果圖像是半透明的,就要考慮重復疊加導致的問題了,而如果是完全不透明的圖形則不會有任何問題。

  背景會在背景圖和黑色背景之間來回閃。

  這個問題其實是源于SurfaceView的雙緩沖機制,我理解就是它會緩沖前兩幀的圖像交替傳遞給后面的幀用作覆蓋,這樣由于我們僅在第一幀繪制了背景,第二幀就是無背景狀態了,且通過雙緩沖機制一直保持下來,解決辦法就是改為在前兩幀都進行背景繪制:

image  現在就沒有問題了(如果換成個不透明的圖形的話就真沒問題了):

image  現在雖然還是達不到最大幀頻,但是也算不錯啦,在真機上跑的會更快些,接近最大幀頻了。

  結語

  我這也是剛接觸Android開發,分享這點心得出來,有寫的不對的歡迎指點一二^^

5
0
 
標簽:Android
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()