天天看點

Android學習之界面篇(九)SurfaceView簡單學習SurfaceHolderSurfaceHolder.Callback

知識點:

  1. surfaceview介紹
  2. surfaceview與View的差別,surface與surfaceview的差別
  3. 執行個體建立與使用
  4. SurfaceHolder.Callback的使用,surfaceHolder可以看作是surfaceview的控制器,控制圖形的大小,像素等.
  5. 在主函數中回調函數的使用getHolder.addCallback(this)
  6. canvas的鎖定與解鎖,繪制圖形之前鎖定畫布,繪制結束之後解鎖畫布。
  7. canvas的save與restore, canvas畫布的各種繪制操作
  8. Timer 的使用與操作

一、SurfaceView簡介:       在Android系統中,有一種特殊的視圖,稱為SurfaceView,它擁有獨立的繪圖表面,即它不與其宿主視窗共享同一個繪圖表面。由于擁有獨立的繪圖表面,是以SurfaceView的UI就可以在一個獨立的線程中進行繪制。又由于不會占用主線程資源,SurfaceView一方面可以實作複雜而高效的UI,另一方面又不會導緻使用者輸入得不到及時響應。        SurfaceView是View類的繼承類,這個View裡内嵌了一個專門用于繪制的Surface,這個可以類似的了解成為一個在View裡的Canvas。你可以控制這個Surface的格式和尺寸。Surfaceview類則控制這個Surface在螢幕上的正确位置。

java.lang.Object
android.view.View
android.view.SurfaceView

二、SurfaceView與view的差別:

Android遊戲當中主要的除了控制類外就是顯示類View。SurfaceView是從View基類中派生出來的顯示類。android遊戲開發中常用的三種視圖是:view、SurfaceView和GLSurfaceView。

  View:顯示視圖,内置畫布,提供圖形繪制函數、觸屏事件、按鍵事件函數等;必須在UI主線程内更新畫面,速度較慢。

  SurfaceView:基于view視圖進行拓展的視圖類,更适合2D遊戲的開發;是view的子類,類似使用雙緩機制,在新的線程中更新畫面是以重新整理界面速度比view快。

   GLSurfaceView:基于SurfaceView視圖再次進行拓展的視圖類,專用于3D遊戲開發的視圖; 是SurfaceView的子類,openGL專用。

在2D遊戲開發中,大緻可以分為兩種遊戲架構,View和SurfaceView。

   View:必須在UI的主線程中更新畫面,用于被動更新畫面。

  surfaceView:UI線程和子線程中都可以。在一個新啟動的線程中重新繪制畫面,主動更新畫面。

     UI的主線程中更新畫面 可能會引發問題,比如你更新畫面的時間過長,那麼你的主UI線程會被你正在畫的函數阻塞。那麼将無法響應按鍵,觸屏等消息。 當使用surfaceView 由于是在新的線程中更新畫面是以不會阻塞你的UI主線程。但這也帶來了另外一個問題,就是事件同步,涉及到線程同步。 是以基于以上,根據遊戲特點,一般分成兩類。 

1 被動更新畫面的。比如棋類,這種用view就好了。因為畫面的更新是依賴于 onTouch 來更新,可以直接使用 invalidate。 因為這種情況下,這一次Touch和下一次的Touch需要的時間比較長些,不會産生影響。 

2 主動更新。比如一個人在一直跑動。這就需要一個單獨的thread不停的重繪人的狀态,避免阻塞main UI thread。是以顯然view不合适,需要surfaceView來控制。 

三、SurfaceView與Surface的聯系

簡單來說,SurfaceView與Surface的聯系就是,Surface是管理顯示内容的資料(implementsParcelable),包括存儲于資料的交換。而SurfaceView就是把這些資料顯示出來到螢幕上面。

兩者聯系如圖所示:

Android學習之界面篇(九)SurfaceView簡單學習SurfaceHolderSurfaceHolder.Callback

四、下面是使用SurfaceView的簡單執行個體:

public class MyView extends SurfaceView implements SurfaceHolder.Callback{

    //聲明一個畫筆     private Paint paint=null;

        public MyView(Context context) {         super(context);         //執行個體化畫筆         paint=new Paint();         //給畫筆設定顔色         paint.setColor(Color.RED);         getHolder().addCallback(this);//添加回調     }

        public void draw(){         //通過getHolder來鎖定畫布         Canvas canvas= getHolder().lockCanvas();         //初始化畫布,設定顔色為白色         canvas.drawColor(Color.WHITE);

        //繪制一個正方形         //canvas.drawRect(0,0,500,500,paint);

        //繪制兩條平行線,getWidth是擷取螢幕的寬度,getHeight是擷取螢幕的高度         //儲存畫布狀态,之後可以對圖形進行更改         canvas.save();         //順時針旋轉畫布,并設定旋轉的中心點的x和y坐标         canvas.rotate(90,getWidth()/2,getHeight()/2);         canvas.drawLine(0,getHeight()/2,getWidth(),getHeight(),paint);         //更改完成之後要複原畫布狀态,save和restore必須成對出現。不執行restore的話以後繪制的圖形都會随着畫布的改變而改變         canvas.restore();         canvas.drawLine(0,getHeight()/2+100,getWidth(),getHeight()+100,paint);

        //在繪制結束後一定要解鎖畫布         getHolder().unlockCanvasAndPost(canvas);     }         @Override     public void surfaceCreated(SurfaceHolder holder) {         draw();     }

        @Override     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

        @Override     public void surfaceDestroyed(SurfaceHolder holder) {

    } }

具體顯示效果如下:

Android學習之界面篇(九)SurfaceView簡單學習SurfaceHolderSurfaceHolder.Callback
Android學習之界面篇(九)SurfaceView簡單學習SurfaceHolderSurfaceHolder.Callback
Android學習之界面篇(九)SurfaceView簡單學習SurfaceHolderSurfaceHolder.Callback

五、SurfaceHoldery與SurfaceHolder.Callbback的介紹

SurfaceHolder

android.view.SurfaceHolder

Class Overview

Abstract interface to someone holding a display surface. Allows you to control the surface size and format, edit the pixels in the surface, and monitor changes to the surface. This interface is typically available through the

SurfaceView

 class.

When using this interface from a thread other than the one running its 

SurfaceView

, you will want to carefully read the methods

lockCanvas()

and

Callback.surfaceCreated()

.

簡單翻譯:

        SurfaceHolder是控制surface的一個抽象接口,你可以通過SurfaceHolder來控制surface的尺寸和格式,或者修改surface的像素,監視surface的變化等等,SurfaceHolder是SurfaceView的典型接口。         與直接控制SurfaceView來修改surface不同,使用SurfaceHolder來修改surface時,需要注意

lockCanvas()

 和

Callback.surfaceCreated()

.這兩個方法。

SurfaceHolder控制surface的流程所使用的幾個方法。

1、 abstract void addCallback(SurfaceHolder.Callback callback)              Add a Callback interface for this holder.// 給SurfaceHolder一個回調對象。

2、 abstract Canvas lockCanvas(Rect dirty)              Just like lockCanvas() but allows specification of a dirty rectangle.

             // 鎖定畫布中的某一個區域,傳回的畫布對象Canvas(當更新的内容隻有一個區域時,同時要追求高效,可以隻更

             新一部分的區域,而不必更新全部畫布區域)

3、 abstract Canvas lockCanvas()              Start editing the pixels in the surface.// 鎖定畫布,傳回的畫布對象Canvas

4、 abstract void    removeCallback(SurfaceHolder.Callback callback)              Removes a previously added Callback interface from this holder.//移除回調對象

5、 abstract void unlockCanvasAndPost(Canvas canvas)              Finish editing pixels in the surface.// 結束鎖定畫圖,并送出改變。

SurfaceHolder.Callback

android.view.SurfaceHolder.Callback
Known Indirect Subclasses GLSurfaceView, NativeActivity, RSSurfaceView, SurfaceHolder.Callback2

Class Overview

A client may implement this interface to receive information about changes to the surface. When used with a

SurfaceView

, the Surface being held is only available between calls to

surfaceCreated(SurfaceHolder)

and

surfaceDestroyed(SurfaceHolder)

. The Callback is set with

SurfaceHolder.addCallback

 method.

簡單翻譯:

SurfaceHolder.Callback是監聽surface改變的一個接口

1、public abstract voidsurfaceChanged(SurfaceHolder holder, int format, int width, int height)

holder The SurfaceHolder whose surface has changed.
format The new PixelFormat of the surface.
width The new width of the surface.
height The new height of the surfa

//surface發生改變時被調用

2、public abstract voidsurfaceCreated(SurfaceHolder holder)

Parameters
holder The SurfaceHolder whose surface is being created

//在surface建立時被調用,一般在這個方法裡面開啟渲染螢幕的線程。

3、public abstract voidsurfaceDestroyed(SurfaceHolder holder)

Parameters
holder The SurfaceHolder whose surface is being destroyed.

   //銷毀時被調用,一般在這個方法裡将渲染的線程停止。

六、android canvas

首先說一下canvas類:

Class Overview

The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing). 

這個類相當于一個畫布,你可以在裡面畫很多東西;

我們可以把這個Canvas了解成系統提供給我們的一塊記憶體區域(但實際上它隻是一套畫圖的API,真正的記憶體是下面的Bitmap),而且它還提供了一整套對這個記憶體區域進行操作的方法,所有的這些操作都是畫圖API。也就是說在這種方式下我們已經能一筆一劃或者使用Graphic來畫我們所需要的東西了,要畫什麼要顯示什麼都由我們自己控制。

這種方式根據環境還分為兩種:一種就是使用普通View的canvas畫圖,還有一種就是使用專門的SurfaceView的canvas來畫圖。兩種的主要是差別就是可以在SurfaceView中定義一個專門的線程來完成畫圖工作,應用程式不需要等待View的刷圖,提高性能。前面一種适合處理量比較小,幀率比較小的動畫,比如說象棋遊戲之類的;而後一種主要用在遊戲,高品質動畫方面的畫圖。

下面是Canvas類常用的方法:

drawRect(RectF rect, Paint paint) //繪制區域,參數一為RectF一個區域 

drawPath(Path path, Paint paint) //繪制一個路徑,參數一為Path路徑對象

drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)  //貼圖,參數一就是我們正常的Bitmap對象,參數二是源區域(這裡是bitmap),參數三是目标區域(應該在canvas的位置和大小),參數四是Paint畫刷對象,因為用到了縮放和拉伸的可能,當原始Rect不等于目标Rect時性能将會有大幅損失。

drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //畫線,參數一起始點的x軸位置,參數二起始點的y軸位置,參數三終點的x軸水準位置,參數四y軸垂直位置,最後一個參數為Paint 畫刷對象。

drawPoint(float x, float y, Paint paint) //畫點,參數一水準x軸,參數二垂直y軸,第三個參數為Paint對象。

drawText(String text, float x, floaty, Paint paint)  //渲染文本,Canvas類除了上面的還可以描繪文字,參數一是String類型的文本,參數二x軸,參數三y軸,參數四是Paint對象。 drawCircle(float cx,float cy,float radius,Paint paint) // 繪制圓,參數一是中心點的x軸,參數二是中心點的y軸,參數三是半徑,參數四是paint對象;

還要了解一個paint類:

Class Overview

The Paint class holds the style and color information about how to draw geometries, text and bitmaps. 

paint類擁有風格和顔色資訊如何繪制幾何學,文本和位圖。

Paint 代表了Canvas上的畫筆、畫刷、顔料等等;

Paint類常用方法:

setARGB(int a, int r, int g, int b) // 設定 Paint對象顔色,參數一為alpha透明值

setAlpha(int a) // 設定alpha不透明度,範圍為0~255

setAntiAlias(boolean aa) // 是否抗鋸齒

setColor(int color)  // 設定顔色,這裡Android内部定義的有Color類包含了一些常見顔色定義

setTextScaleX(float scaleX)  // 設定文本縮放倍數,1.0f為原始

setTextSize(float textSize)  // 設定字型大小

setUnderlineText(booleanunderlineText)  // 設定下劃線

七、Java Timer和TimerTask簡介

Timer是一種線程設施,用于安排以後在背景線程中執行的任務。可安排任務執行一次,或者定期重複執行,可以看成一個定時器,可以排程TimerTask。TimerTask是一個抽象類,實作了Runnable接口,是以具備了多線程的能力。

import java.util.TimerTask; public class MyTask extends TimerTask {     private int id;     public MyTask(int id)     {         this.id = id;     }     public void run()     {         System.out.println("線程" + id + ":正在執行"); //System.gc();     } }

然後主程式代碼為:

import java.util.Date; import java.util.Timer; public class Test { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new MyTask(1), 5000);// 5秒後啟動任務 MyTask secondTask = new MyTask(2); timer.schedule(secondTask, 1000, 3000); // 1秒後啟動任務,以後每隔3秒執行一次線程 Date date = new Date(); timer.schedule(new MyTask(3), new Date(date.getTime() + 1000)); //      以date為參數,指定某個時間點執行線程 //      timer.cancel(); //      secondTask.cancel(); System.out.println("main thread 結束!"); } }

Timer裡面有4個schedule重載函數,而且還有兩個scheduleAtFixedRate: void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 安排指定的任務在指定的時間開始進行重複的固定速率執行。 void scheduleAtFixedRate(TimerTask task, long delay, long period)

安排指定的任務在指定的延遲後開始進行重複的固定速率執行。

使用scheduleAtFixedRate的話,Timer會盡量的讓任務在一個固定的頻率下運作。例如:在上面的例子中,讓secondTask在1秒鐘後,每3秒鐘執行一次,但是因為java不是實時的,是以,我們在上個程式中表達的原義并不能夠嚴格執行,例如有時可能資源排程緊張4秒以後才執行下一次,有時候又3.5秒執行。如果我們調用的是scheduleAtFixedRate,那麼Timer會盡量讓你的secondTask執行的頻率保持在3秒一次。運作上面的程式,假設使用的是scheduleAtFixedRate,那麼下面的場景就是可能的:1秒鐘後,secondTask執行一次,因為系統繁忙,之後的3.5秒後secondTask才得以執行第二次,然後Timer記下了這個延遲,并嘗試在下一個任務的時候彌補這個延遲,那麼2.5秒後,secondTask将執行的三次。“以固定的頻率而不是固定的延遲時間去執行一個任務”就是這個意思。

Timer終止的問題:

預設情況下,隻要一個程式的timer線程在運作,那麼這個程式就會保持運作。可以通過以下3種方法終止一個timer線程:

(1)調用timer的cancle方法。你可以從程式的任何地方調用此方法,甚至在一個timer task的run方法裡;

(2)讓timer線程成為一個daemon線程(可以在建立timer時使用new Timer(true)達到這個目地),這樣當程式隻有daemon線程的時候,它就會自動終止運作;

(3)調用System.exit方法,使整個程式(所有線程)終止。

TimerTask也有cancel方法。

上面所說的“隻要一個程式的timer線程在運作,那麼這個程式就會保持運作”。那麼反過來,如果Timer裡的所有TimerTask都執行完了,整個程式會退出嗎,經測試答案是否定的,例如上面的測試代碼,如果隻加第一個TimerTask在Timer中執行:

timer.schedule(new MyTask(1), 5000);// 5秒後啟動任務 那麼5秒以後,其實整個程式還是沒有退出,Timer會等待垃圾回收的時候被回收掉然後程式會得以退出,但是多長時間呢?在TimerTask的run函數執行完以後加上System.gc();就可以了。

繼續閱讀