Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android SurfaceView 繪圖覆蓋刷新及髒矩形刷新方法

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

編輯:Android開發實例

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

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

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);// 解鎖畫布,提交畫好的圖像

                  }

              }

           }

 

       }

 

    }

}

模擬器中的運行效果:

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

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

 

髒矩形刷新

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

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

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

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

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

 

覆蓋刷新

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

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

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

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

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

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

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

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

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

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

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

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

==============================以下是上文的修正==================================

幀頻處理

今天我在審視上篇示例代碼時猛然發現我犯了個低級錯誤,致使幀頻達不到預期,這個錯誤在這裡:

這裡設置每次繪制之後固定休眠33毫秒,以此來限制每秒幀頻在30幀。

但實際上這裡忽視了繪圖及其他運算所消耗的時間,也就是說除非其他所有操作的總耗時都不足1/3毫秒,否則我們就根本無法達到每秒30幀的期望。

修正方法是在每幀開始處理前獲取系統當前時間值,然後在處理完畢後再獲取一次當前時間值,然後用當前值減去舊值,就得到了處理所消耗的時間,然後用33毫秒減去得到的消耗時間,就是我們此幀接下來應當休眠的時間值了:

現在運行一下程序,就能看到跑得很流暢了。

我想可能從事過游戲開發的人都能一眼看出這個錯誤吧,我是沒經驗在這栽了一下>_<~

 

繪圖處理

在繪圖處理時,還有一點需要注意的就是:

鎖定Canvas之後到解鎖這段時間內,最好僅執行與繪圖有關的操作,其他代碼越少越好,就好像往常我們開發時打開數據庫鏈接一樣。

因為我們鎖定後其他線程如果要使用的話就得等待了,所以盡快釋放會節省其他線程的等待時間,要盡量把無關緊要的代碼放在這個時間段以外執行。

針對此例而言,就是那個有關矩陣部分的操作可以調整到前面去執行,Paint對象也可以挪到前面去創建,而且不需要每次循環都創建一個Paint對象,創建一次就足矣:

經過調整之後,綠色高亮部分就只剩下繪圖相關的代碼了,減負了不少呵。

轉自:http://www.cnblogs.com/SkyD/archive/2010/11/08/1871423.html

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved