Android アプリで画面上にグラフィックスを描画する場合、View クラスを使用する方法の他に、SurfaceView クラスを使用する方法があります。SurfaceView クラスを使用した場合、アプリケーションとは別に描画処理のスレッドが独立しており、より高速に安定して再描画が行われます。そのため、ゲームや動画再生などの用途に向いているようです。
SurfaceView クラスを継承した MySurfaceView を定義しますMySurfaceView は、SurfaceHolder.Callback インタフェースを実装しますMySurfaceView は、アニメーション動作のために Runnable インタフェースを実装しますMySurfaceView について、SurfaceHolder.CallbackMySurfaceView について、アニメーション処理を行う 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 の内容が画面に反映されます。