SurfaceViewで高速描画
ゲームなどの描画に時間のかかる処理をするときは普通のViewで描画するよりSurfaceViewを使った方が便利です。
なぜかというとViewと比べてSurfaceViewには次のような利点があるからです。
- スレッドで描画処理できるから高速
- UIスレッドの邪魔をしない
- 大きさやフォーマットを変えることができる
重要なのは1番目のUIスレッド以外のスレッドから描画処理できるということです。
スレッドを使うのでViewと比べてかなり高速に描画できます。
サンプル
ではSurfaceViewを使ってなぞった軌跡に沿って1筆書きするビューを例として紹介します。
class DrawingView extend SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder holder; //サーフェイスの大きさやフォーマットを変えたり、描画するインターフェイス private Thread thread; //描画に使うスレッド private Paint paint = new Paint(); public DrawingView(Context context) { super(context); init(); } public DrawingView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public void init() { setZOrderOnTop(true); /// このビューの描画内容がウインドウの最前面に来るようにする。 holder = getHolder(); holder.addCallback(this); paint.setColor(Color.BLUE); } /*サーフェイスの大きさやフォーマットが変わった時の処理*/ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){} /*サーフェイスが初めて作られたときに呼ばれる、*/ @Override public void surfaceCreated(SurfaceHolder holder) { thread = new Thread(this); thread.start(); } /*サーフェイスが壊される直前に呼ばれる。*/ @Override public void surfaceDestroyed(SurfaceHolder holder) {} /**描画スレッドを実行する。*/ @Override public void run() { Canvas canvas = null; try{ canvas = holder.lockCanvas(null); //キャンバスをロック synchronized(holder){ //キャンバスに図形を描画 canvas.drawColor(Color.WHITE); int pointCount = pointArray.size(); for(int i = 0; i + 3 < pointCount; i += 2){ try{ canvas.drawLine(pointArray.get(i), pointArray.get(i + 1), pointArray.get(i + 2), pointArray.get(i + 3), new Paint()); }catch(Exception err){ } } } }finally{ if(canvas != null){ holder.unlockCanvasAndPost(canvas); //ロックを解除して描画 } } } /**スレッドを初期化して実行する。*/ private void drawOnThread() { thread = new Thread(this); thread.start(); } List<Float> pointArray = new ArrayList<Float>(); //なぞられた点を記録するリスト @Override public boolean onTouchEvent(MotionEvent event) { switch(event.getAction()){ case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: pointArray.add(event.getX()); pointArray.add(event.getY()); drawOnThread(); //描画 break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: pointArray.clear(); //1筆書きをクリア break; } return super.onTouchEvent(event); } }
このビューでは画面がタップされたら1筆書きを開始し、タッチされている間は再描画し続け、タッチが離されたら1筆書きを終了するだけのビューです。
実際に1筆書きしてみると次のような図形が描けました。
もしこれと同じことをViewを使ってやろうとすると再描画し続けるのにとても時間がかかるので途中で止まったり、止まらなくてもかくかくした線になってしまうと思います。
説明
SurfaceViewにはSurfaceHolder.Callbackインターフェイスを実装しています。
このインターフェイスには次の3つのメソッドが定義されています。
- surfaceCreated
ビューのサーフェイスが初期化されたときに呼ばれます。これより前の描画処理はできません。
- surfaceChanged
サーフェイスの大きさやフォーマットが変わると呼び出されます。
- surfaceDestroyed
サーフェイスが終了するときに呼ばれます。
この3つのメソッドをサンプルのクラスでは実装しています。
また、クラス内でスレッドを実行するためにRunnableインターフェイスも取り入れています。
それでコンストラクタから呼ばれるinitメソッド内で次のような処理があります。
setZOrderOnTop(true); holder = getHolder(); holder.addCallback(this);
setZOrderOnTopメソッドは必ず設定しなくていいみたいですが、描画内容が常に最前面に来るようにtrueに設定しています。
それからSurfaceHolderを取得し、クラス自身をコールバックとして登録しています。
描画はスレッドのrunメソッドで実行しています。
@Override public void run() { Canvas canvas = null; try{ canvas = holder.lockCanvas(); //キャンバスをロック synchronized(holder){ /*描画処理(省略)*/ } }finally{ if(canvas != null){ holder.unlockCanvasAndPost(canvas); //ロックを解除して描画 } } }
SurfaceHolderのlockCanvasメソッドでビューへの描画を開始し、unlockCanvasAndPostメソッドで描画を終了して画面に内容を反映しています。
この2つのメソッドは必ず対で呼ぶ必要があります。
この例で重要なのはこれくらいで後は普通のViewに描画するのと変わりません。ただ違うのはスレッドを使っているということだけです。
もっとSurfaceViewについてもっと知りたい場合はSurfaceViewのリファレンスが参考になります。
以上、SurfaceViewの使い方でした。お疲れ様でした!