AndroidのSurfaceViewの基礎

  • 164
    Like
  • 1
    Comment
More than 1 year has passed since last update.

簡単に

  • アニメーションとかゲームみたいに描画のパフォーマンスが必要なもの、凝ったグラフィックを描画したい場合はSurfaceView使え。
  • メインスレッド以外でも描画可能だけど、スレッド間での排他処理とかは気をつけてね。
  • SurfaceへのアクセスはSurfaceHolderインターフェイス経由で行うよ。

普通のViewとSurfaceViewの違い

- 普通のView SurfaceView
描画処理 メインスレッドのみ バックグラウンドスレッドでも可能
使い道 静的なコンテンツ(意味深)に最適 常に更新されるような描画内容に最適

疑問

Surfaceってなに?

画面に描画される内容の生のバッファ(のハンドル)

SurfaceHolderは?

Surfaceのピクセルを実際にいじったり、Surfaceの変化を監視する人のためのインターフェイス。SurfaceViewにはgetHolder()メソッドが用意されていて、そのSurfaceViewのホルダーのインスタンスを取得できる。

  public MySurfaceView(Context context) {
    super(context);
    SurfaceHolder holder = getHolder(); // でもこのタイミングではまだSurfaceの準備ができてない
  }

その前に、知っておかなければ行けないAndroidのグラフィック描画のための4要素

ビットマップ

実際にスクリーンに転送される最終的なピクセルの配列。

キャンバス(Canvas)

「ここに青い四角を描く」「ここに白い丸を描く」というような命令をビットマップの形にするための機構。

プリミティブ

四角(Rect)とか文字列(Text)とかのこと。

ペイント(Paint)

色とかスタイルとか、プリミティブを描画する時の属性的な情報。

SurfaceViewの基本的な使い方

バックグラウンドスレッドで描画処理できるのが肝だけど、基本を理解するにはとりあえずメインスレッドで一回だけ描画するような使い方をしてみるのがわかりやすい。

基本的には、SurfaceViewを継承したサブクラスを用意して、その中で独自の描画の処理を書いてく。

public class MySurfaceView extends SurfaceView

SurfaceViewは自身のSurfaceの状態変化についても監視する必要がある(途中でサイズが変わったら、それに応じて描画内容も調整したりとか)ので、実際にはSurfaceHolderからのコールバックを受け取るためのSurfaceHolder.Callbackをimplementすることになる。

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback

SurfaceViewのコンストラクタの中で、ホルダーのコールバックに自身をセットしておけば、Surfaceの準備ができたタイミング(実際に描画できるようになったタイミング)等でコールバックが呼ばれるようになる。

public MySurfaceView(Context context) {
    super(context);
    getHolder().addCallback(this);
}

SurfaceHolder.Callbackで実装しなければいけないメソッドは下記の三つ。各メソッドが呼ばれるタイミングは名前の通り、Surfaceが作られた時/変化があった時/破棄された時。

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

surfaceCreatedあるいはsurfaceChangedが呼ばれたタイミングでは、実際にSurfaceに描画する準備ができている。都合良く引数でSurfaceHolderのインスタンスまで渡してくれている。

public void surfaceCreated(SurfaceHolder holder) {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.BLUE);
    paint.setStyle(Style.FILL);

    Canvas canvas = holder.lockCanvas();
    canvas.drawColor(Color.BLACK);
    canvas.drawCircle(100, 200, 50, paint);
    holder.unlockCanvasAndPost(canvas);
}
// https://www.linux.com/learn/tutorials/707993-how-to-draw-2d-object-in-android-with-a-canvas の内容を少し改変して使用

SurfaceHolderからはCanvasを取得できるので、そこに対して描画命令を出していく。lockCanvas()unlockCanvasAndPost()はマルチスレッド用の排他処理だけど、Canvasの取得と描画内容の反映のために必然的にこのペアを使うことになるので、強く意識しなくても大丈夫かも。

注意事項・おまけ

  • SurfaceViewの上に普通のUI要素(ボタンとか)を重ねてもOKだけど、パフォーマンスは悪化するよ。
  • OpenGLで描画したい場合はGLSurfaceViewというサブクラスがあるよ。

参考