Android アプリで画面上にグラフィックスを描画する場合、View クラスを使用する方法の他に、SurfaceView クラスを使用する方法があります。SurfaceView クラスを使用した場合、アプリケーションとは別に描画処理のスレッドが独立しており、より高速に安定して再描画が行われます。そのため、ゲームや動画再生などの用途に向いているようです。
SurfaceView
クラスを継承した MySurfaceView
を定義しますMySurfaceView
は、SurfaceHolder.Callback
インタフェースを実装しますMySurfaceView
は、アニメーション動作のために Runnable
インタフェースを実装しますMySurfaceView
について、SurfaceHolder.Callback
MySurfaceView
について、アニメーション処理を行う run
メソッドを実装しますMySurfaceView
を setContentView
します画面上をボールが跳ね回るアニメーションを作成してみました。スクリーンショットでは 10 フレーム程度の残像しか見えませんが、実機の液晶パネルでは、かなり長い残像が見えます。手元の LG L-05D で動作させたところ、60 FPS で動作していましたので、アクション ゲームなどの高速描画が求められる使途にも十分です。
package com.example.helloworld; import android.support.v7.app.ActionBarActivity; import android.content.Context; import android.os.Bundle; import android.view.SurfaceView; import android.view.SurfaceHolder; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; public class MainActivity extends ActionBarActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView( new MySurfaceView( this )); } } class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { public MySurfaceView(Context context) { super(context); getHolder().addCallback(this); } private boolean mIsAttached; private Thread mThread; public void surfaceCreated(SurfaceHolder holder) { mIsAttached = true; mThread = new Thread(this); } private float mScreenWidth, mScreenHeight; public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mScreenWidth = width; mScreenHeight = height; mThread.start(); } public void surfaceDestroyed(SurfaceHolder holder) { mIsAttached = false; while( mThread != null && mThread.isAlive()); mThread = null; } public void run() { float vx = 5, vy = 5; float xOffset = 100, yOffset = 100; float radius = 50; long now = System.currentTimeMillis(); // FPS 計測用 while (mIsAttached) { if (xOffset <= radius || mScreenWidth - radius <= xOffset) vx = -vx; // 画面の両端を超えたらバウンドさせる if (yOffset <= radius) vy = -vy; // 画面の上端を超えたらバウンドさせる if (mScreenHeight - radius <= yOffset) { yOffset = mScreenHeight - radius; vy = -70; // 画面の下端を超えたらバウンドさせる } xOffset += vx; yOffset += vy; vy += 5; // 重力加速度 SurfaceHolder holder = getHolder(); Canvas canvas = holder.lockCanvas(); if( canvas != null ) { Paint paint = new Paint(); paint.setColor(Color.GREEN); canvas.drawColor(Color.argb(64, 0,0,0)); canvas.drawCircle(xOffset, yOffset, radius, paint); long now_next = System.currentTimeMillis(); canvas.drawText(String.format("%6.2f", 1000/(float)(now_next - now)), 30, 30, paint); now = now_next; holder.unlockCanvasAndPost(canvas); } } } }
SurfaceHolder.Callback
として、自身を登録しています。SurfaceView
が生成されたタイミングで、描画用スレッド(mThread)を生成して、スレッドを開始しています。SurfaceView
のピクセルフォーマットや描画エリアのサイズが変化(確定)したら呼ばれます。SurfaceView
が破棄されるタイミングで呼ばれます。描画用スレッド(mThread)を終了するためにフラグ(mIsAttached)を下して、スレッドが終了するのを待っています。while
でグルグル回しています。SurfaceHolder
を取得してから、描画用の Canvas
を取得しています。取得した Canvas は、表示されている画面とは別のオフ スクリーン バッファとなっており、排他ロックされています。Canvas
の妥当性チェックが省略されていて、アプリケーションを終了する際に、SurfaceView
が破棄されたタイミングと、描画スレッドの動作タイミングに依っては、アプリケーションの終了時にエラー メッセージが表示されることがあります。SurfaceHolder
から取得した Canvas
のロックを解除します。このタイミングで Canvas
の内容が画面に反映されます。