2011年10月3日月曜日

SurfaceViewによる描画

グラフィックス(1)-Viewクラスへの描画 では、グラフィックの描画にViewクラスを使っていましたが、これをSurfaceViewクラスを使って実現する事もできます。

SurfaceViewは、Viewクラスを継承したクラスですが、Viewクラスよりも高速に描画ができ、ゲームプログラミング等に適しています。

通常のウィジェットと違い、別スレッドで直接GUI操作をおこなう事もできます。

 

今回は「グラフィックス(1)-Viewクラスへの描画」で紹介した、リスト1〜リスト3のプログラムを、 SurfaceViewで実現するにはどのようにしたらよいか、についてみていきます。

SurfaceViewによるグラフィックスの基本形-静止した画像の描画

「グラフィックス(1)-Viewクラスへの描画」のリスト1では、 Viewを使って、静止した画像を描画していましたが、これをSurfaceViewを使って、実現してみます。

リスト4(SurfaceViewSample1.java

01

package gudon.sample.surfaceview1;

02

 

 

03

import android.app.Activity;

04

import android.content.Context;

 

05

import android.graphics.Canvas;

06

import android.graphics.Color;

 

07

import android.graphics.Paint;

08

import android.os.Bundle;

 

09

import android.view.SurfaceHolder;

10

import android.view.SurfaceView;

 

11

 

12

public class SurfaceViewSample1 extends Activity {

 

13

 

14

    @Override

 

15

    public void onCreate(Bundle savedInstanceState) {

16

        super.onCreate(savedInstanceState);

 

17

 

18

        setContentView(new CustomSurfaceView(this));

 

19

    }

20

}

 

21

 

22

class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

 

23

 

24

    public CustomSurfaceView(Context context) {

 

25

        super(context);

26

         

 

27

        getHolder().addCallback(this);

28

    }

 

29

 

30

    @Override

 

31

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

32

        // SurfaceViewが変化(画面の大きさ,ピクセルフォーマット)した時のイベントの処理を記述

 

33

    }

34

 

 

35

    @Override

36

    public void surfaceCreated(SurfaceHolder holder) {

 

37

        // SurfaceViewが作成された時の処理(初期画面の描画等)を記述

38

        Canvas canvas = holder.lockCanvas();

 

39

 

40

        // この間にグラフィック描画のコードを記述する。

 

41

 

42

        Paint paint = new Paint();

 

43

        paint.setColor(Color.GREEN);

44

        canvas.drawRect(0, 0, 50, 50, paint);

 

45

 

46

        // この間にグラフィック描画のコードを記述する。

 

47

 

48

        holder.unlockCanvasAndPost(canvas);

 

49

    }

50

 

 

51

    @Override

52

    public void surfaceDestroyed(SurfaceHolder holder) {

 

53

        // SurfaceViewが廃棄された時の処理を記述

54

    }

 

55

}

SurfaceViewを操作するには、SurfaceHolderオブジェクトを使います。

例えば、27行目ではgetHolderメソッドでSurfaceHolderオブジェクトを取得して、SurfaceHolderオブジェクトのaddCallbackメソッドを使って、SurfaceViewの状態が変化した時に呼び出される、SurfaceHolder.Callbackインターフェースを実装したクラスを、指定しています。

通常は、この例のようにSurfaceViewクラス自身に、SurfaceHolder.Callbackインターフェースを実装する事が多いと思います。

SurfaceHolder.Callbackインターフェースを実装するには、以下の3つのコールバックメソッドを、オーバーライドする必要があります。

·                       surfaceChanged
SurfaceView
が変化(画面の大きさ,ピクセルフォーマット)した時のイベントの処理を記述する。

·                       surfaceCreated
SurfaceView
が作成された時の処理(初期画面の描画等)を記述する。

·                       surfaceDestroyed
リソースの開放等、SurfaceViewが廃棄された時の処理を記述する。

SurfaceViewに、グラフィックを描画するには、38行目〜48行目のようにSurfaceHolderlockCanvasメソッドを使って、 他のスレッドからCanvasオブジェクトを操作されないように、Canvasをロックした上で、Canvasオブジェクトを取得し、 描画をおこないます。

SurfaceViewでのCanvasへの描画の方法は、Viewクラスでおこなった描画の方法とまったく同じです。

描画が終わったら、unlockCanvasAndPostメソッドを使って、すみやかにCanvasオブジェクトを開放します。

SurfaceHolderオブジェクトは、ここでは、surfaceCreatedメソッドの引数として渡されているものを使っていますが、 27行目のようにgetHolderメソッドを使って取得する事もできます。

SurfaceViewによるユーザからのイベントに対する応答

SurfaceViewに対するユーザからのイベントの処理も、基本的にはViewでの処理とまったく同じです。

リスト2の、Viewによるイベント処理のプログラムの例を、SurfaceViewを使って実現してみます。

リスト5(SurfaceViewSample2.java

001

package gudon.sample.surfaceview2;

002

 

 

003

import android.app.Activity;

004

import android.content.Context;

 

005

import android.graphics.Canvas;

006

import android.graphics.Color;

 

007

import android.graphics.Paint;

008

import android.os.Bundle;

 

009

import android.view.KeyEvent;

010

import android.view.MotionEvent;

 

011

import android.view.SurfaceHolder;

012

import android.view.SurfaceView;

 

013

 

014

public class SurfaceViewSample2 extends Activity {

 

015

 

016

    @Override

 

017

    public void onCreate(Bundle savedInstanceState) {

018

        super.onCreate(savedInstanceState);

 

019

 

020

        setContentView(new CustomSurfaceView(this));

 

021

    }

022

}

 

023

 

024

class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

 

025

 

026

    private static final float rectWidth = 50;

 

027

    private static final float rectHeight = 50;

028

 

 

029

    private float x0 = 0, y0 = 0;

030

    private float dx = 0, dy = 0;

 

031

    private float xOffset = 0, yOffset = 0;

032

 

 

033

    public CustomSurfaceView(Context context) {

034

        super(context);

 

035

 

036

        setFocusable(true);

 

037

        getHolder().addCallback(this);

038

    }

 

039

 

040

    @Override

 

041

    public void surfaceChanged(SurfaceHolder holder, int format, int width,

042

            int height) {

 

043

        // SurfaceViewが変化(画面の大きさ,ピクセルフォーマット)した時のイベントの処理を記述

044

    }

 

045

 

046

    @Override

 

047

    public void surfaceCreated(SurfaceHolder holder) {

048

        // SurfaceViewが作成された時の処理(初期画面の描画等)を記述

 

049

        doDraw(holder);

050

    }

 

051

 

052

    @Override

 

053

    public void surfaceDestroyed(SurfaceHolder holder) {

054

        // SurfaceViewが廃棄された時の処理を記述

 

055

    }

056

 

 

057

    private void doDraw(SurfaceHolder holder) {

058

        Canvas canvas = holder.lockCanvas();

 

059

        onDraw(canvas);

060

        holder.unlockCanvasAndPost(canvas);

 

061

    }

062

 

 

063

    @Override

064

    protected void onDraw(Canvas canvas) {

 

065

        canvas.drawColor(Color.BLACK);

066

 

 

067

        Paint paint = new Paint();

068

        paint.setColor(Color.GREEN);

 

069

        canvas.drawRect(xOffset + dx, yOffset + dy, xOffset + dx + rectWidth,

070

                yOffset + dy + rectHeight, paint);

 

071

    }

072

 

 

073

    @Override

074

    public boolean onKeyDown(int keyCode, KeyEvent event) {

 

075

        switch (keyCode) {

076

        case KeyEvent.KEYCODE_DPAD_LEFT:

 

077

            xOffset--;

078

            break;

 

079

        case KeyEvent.KEYCODE_DPAD_RIGHT:

080

            xOffset++;

 

081

            break;

082

        case KeyEvent.KEYCODE_DPAD_UP:

 

083

            yOffset--;

084

            break;

 

085

        case KeyEvent.KEYCODE_DPAD_DOWN:

086

            yOffset++;

 

087

            break;

088

        default:

 

089

            return true;

090

        }

 

091

        doDraw(getHolder());

092

        return true;

 

093

    }

094

 

 

095

    @Override

096

    public boolean onTouchEvent(MotionEvent event) {

 

097

        float x = event.getX();

098

        float y = event.getY();

 

099

        switch (event.getAction()) {

100

        case MotionEvent.ACTION_DOWN:

 

101

            x0 = x;

102

            y0 = y;

 

103

            break;

104

        case MotionEvent.ACTION_MOVE:

 

105

            dx = x - x0;

106

            dy = y - y0;

 

107

            break;

108

        case MotionEvent.ACTION_UP:

 

109

            xOffset += (x - x0);

110

            yOffset += (y - y0);

 

111

            dx = 0;

112

            dy = 0;

 

113

            break;

114

        default:

 

115

            return true;

116

        }

 

117

        doDraw(getHolder());

118

        return true;

 

119

    }

120

}

Viewクラスの処理と異なる点は、Viewクラスでは画面の更新時にonDrawメソッドが自動的に呼ばれ、クラフィックの描画がおこなわれていましたが、SurfaceViewでは呼び出されません。

また、画像を再描画したい場合には、invalidateメソッドを実行すれば、onDrawメソッドが呼び出されていましたが、 SurfaceViewでは、invalidateメソッドを実行しても、onDrawメソッドは呼び出されません。

このプログラムでは、doDrawメソッドを定義して画像の更新処理をおこないたい場合は、このメソッドを呼び出すようにしています。

また、Viewクラスの描画処理をイメージしやすいようにonDrawメソッドを定義して、doDrawメソッドからonDrawメソッドを呼び出していますが、 onDrawメソッドが自動的に呼び出されるわけではないので、onDrawメソッドを定義せずに、doDrawメソッド内で直接グラフィックの描画をおこなってもかまいません。

SurfaceViewバージョンの「壁にあたって跳ね返る図形のアニメーション」

SurfaceViewで定期的に画面を書き換える例を示すために、 リスト3の「壁にあたって跳ね返る図形のアニメーション」を, SurfaceViewを使って実現してみます。

リスト6(SurfaceViewSample3.java

01

package gudon.sample.surfaceview3;

02

 

 

03

import android.app.Activity;

04

import android.content.Context;

 

05

import android.graphics.Canvas;

06

import android.graphics.Color;

 

07

import android.graphics.Paint;

08

import android.os.Bundle;

 

09

import android.util.Log;

10

import android.view.SurfaceHolder;

 

11

import android.view.SurfaceView;

12

 

 

13

public class SurfaceViewSample3 extends Activity {

14

 

 

15

    @Override

16

    public void onCreate(Bundle savedInstanceState) {

 

17

        super.onCreate(savedInstanceState);

18

 

 

19

        setContentView(new CustomSurfaceView(this));

20

    }

 

21

}

22

 

 

23

class CustomSurfaceView extends SurfaceView implementsSurfaceHolder.Callback,Runnable{

24

    Thread thread;

 

25

    boolean isAttached;

26

 

 

27

 

28

    private static final float rectWidth = 50;

 

29

    private static final float rectHeight = 50;

30

 

 

31

    private float dx = 5, dy = 5;

32

    private float screenWidth, screenHeight;

 

33

    private float xOffset = 0, yOffset = 0;

34

 

 

35

    public CustomSurfaceView(Context context) {

36

        super(context);

 

37

 

38

        getHolder().addCallback(this);

 

39

    }

40

 

 

41

    @Override

42

    public void surfaceChanged(SurfaceHolder holder, int format, int width,

 

43

            int height) {

44

        screenWidth = width;

 

45

        screenHeight = height;

46

    }

 

47

 

48

    @Override

 

49

    public void surfaceCreated(SurfaceHolder holder) {

50

        isAttached=true;

 

51

        thread = new Thread(this);

52

        thread.start();

 

53

    }

54

 

 

55

    @Override

56

    public void surfaceDestroyed(SurfaceHolder holder) {

 

57

        isAttached = false;

58

        while (thread.isAlive());

 

59

    }

60

 

 

61

    @Override

62

    public void run() {

 

63

        while (isAttached) {

64

            Log.v("SurfaceViewSample3","run");

 

65

 

66

            if (xOffset < 0 || xOffset + rectWidth > screenWidth)

 

67

                dx = -dx;

68

            if (yOffset < 0 || yOffset + rectHeight > screenHeight)

 

69

                dy = -dy;

70

            xOffset += dx;

 

71

            yOffset += dy;

72

 

 

73

            doDraw(getHolder());

74

        }

 

75

    }

76

 

 

77

    private void doDraw(SurfaceHolder holder){

78

        Canvas canvas = holder.lockCanvas();

 

79

 

80

        // この間にグラフィック描画のコードを記述する。

 

81

 

82

        Paint paint = new Paint();

 

83

        paint.setColor(Color.GREEN);

84

 

 

85

        canvas.drawColor(Color.BLACK);

86

        canvas.drawRect(xOffset, yOffset, xOffset + rectWidth, yOffset

 

87

                + rectHeight, paint);

88

 

 

89

        // この間にグラフィック描画のコードを記述する。

90

 

 

91

        holder.unlockCanvasAndPost(canvas);

92

    }

 

93

}

リスト3では、画面のサイズを取得するのに、View#onSizeChangedイベントを使っていましたが、 SurfaceHolder.CallbackインターフェースにはsurfaceChangedメソッドがあるので、ここでは、これを利用しています。

また、リスト3では、定期的に画面を更新するためのタイマーとして、HandlerクラスのsendEmptyMessageDelayedメソッドを使用していましたが、 SurfaceViewは、別スレッドから直接GUI操作ができるので、(SurfaceViewクラスにRunnableインターフェースをインプリメントして)javaで標準に使われているThreadクラスを使っています。

プログラム終了時にスレッドを終了させないと、終了後もスレッドが生き残ってしまうので、この処理をsurfaceDestroyedメソッドに実装しています。

リスト3では、この処理にonDetachedFromWindowイベントを利用していましたが、onDetachedFromWindowイベントはSurfaceViewでは発生しないようです。

58行目のwhileループは必要無いかもしれませんが、一応、確実にスレッドが終了した後にsurfaceDestroyedメソッドが終了するように、待ち時間を持たせるためにいれてあります。

64行目のLog出力は、プログラム終了時にスレッドが停止するか、確認するためのものです。

85行目で、リスト3には無かったcanvas#drawColorメソッドを追加して、背景を黒色に塗りつぶしていますが、 これは、Viewクラスでは前の画面がクリアされた後に画面が描画されるのに対して、 SurfaceViewでは前の画面が残ったままになってしまうため、前の画面を消去するためにいれてあります。

 

0 件のコメント:

コメントを投稿