簡単に
- アニメーションとかゲームみたいに描画のパフォーマンスが必要なもの、凝ったグラフィックを描画したい場合は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
というサブクラスがあるよ。
参考
- http://developer.android.com/reference/android/view/SurfaceView.html
- http://developer.android.com/reference/android/view/Surface.html
- http://developer.android.com/reference/android/view/SurfaceHolder.html
- http://developer.android.com/reference/android/graphics/Canvas.html
- https://www.linux.com/learn/tutorials/707993-how-to-draw-2d-object-in-android-with-a-canvas