SurfaceViewを用いた画面描画 - Android
SurfaceViewを用いて画面描画するコードを紹介します。
プロジェクトの作成
Android アプリケーションプロジェクトを作成します。
[New Android Application]ダイアログボックスが表示されますので、以下を設定します。
- Application Name: "SurfaceView"
- Project Name: "SurfaceView"
- Package Name: "com.iPentec.surfaceview"
- Minimum Required SDK: "API 8: Android 2.2 (Froyo)"
- Target SDK: "API 17: Android 4.2 (Jelly Bean)"
- Compile With: "API 17: Android 4.2 (Jelly Bean)"
- Theme: "Holo Light with Dark Action Bar"
コード
下記のコードを記述します。
MainActivity.java
メインのアクティビティのコードです。
package com.iPentec.surfaceview;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main); //コメントアウト
setContentView(new MySurfaceView(this)); //追加
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
解説
//setContentView(R.layout.activity_main); //コメントアウト
setContentView(new MySurfaceView(this)); //追加
が変更部分です。今回はリソースのXMLレイアウトを使わずに直接SurfaceViewをフォームに配置するため、"setContentView(R.layout.activity_main)"をコメントアウトし、"setContentView(new MySurfaceView(this));"を追記します。
MySurfaceView.java
クラスを追加して以下のコードを記述します。
package com.iPentec.surfaceview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
/**
* TODO: document your custom view class.
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
public MySurfaceView(Context context) {
super(context);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void run() {
}
}
実行結果
プロジェクトを実行します。下図の何も表示されない画面が表示されます。
図形を描画する
上記のプログラムにコードを追加して図形を描画します。
MySurfaceView.java
package com.iPentec.surfaceview;
import android.content.*;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.*;
import android.os.Bundle;
import android.util.*;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private Thread thread;
private SurfaceHolder holder;
public MySurfaceView(Context context){
super(context);
holder = getHolder();
holder.addCallback(this);
holder.setFixedSize(getWidth(), getHeight());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
thread = new Thread(this);
thread.start();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread=null;
}
@Override
public void run() {
while (thread != null) {
doDraw(holder);
}
}
private void doDraw(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas();
if (canvas != null){
Paint paint = new Paint();
paint.setColor(Color.GREEN);
canvas.drawColor(Color.BLACK);
canvas.drawCircle(100, 100, 20, paint);
holder.unlockCanvasAndPost(canvas);
}
}
}
解説
SurfaceView内のrunメソッドでループを作成し、doDrawメソッドを呼び出します。doDrawメソッド内にキャンバスへの描画処理を記述します。上記の例では、描画色を緑にし、(x,y)=(100,100)の位置に半径20の円を描画します。
lockCanvas後にcanvasが取得できずnullになるケースがあるため、lockCanvas()呼出し後にcanvasのnullチェックをしています。(チェックをしない場合、アプリの切り替えやホームボタンでアプリがバックグラウンドに回る際にcanvasがnullのためNullPointerExceptionで落ちるケースがありました。)
実行結果
プロジェクトを実行します。(x,y)=(100,100)の位置に緑色の円が描画されました。
連続して図形を描画する
連続して図形を描画することでアニメーションの動作を実装します。
上記のプログラムのMySurfaceView.javaにコードを追加します。
MySurfaceView.java
package com.iPentec.surfaceview;
import android.content.*;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.*;
import android.os.Bundle;
import android.util.*;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private Thread thread;
private SurfaceHolder holder;
private float dx = 5, dy = 5;
private float screenWidth, screenHeight;
private static final float rectWidth = 40;
private static final float rectHeight = 40;
private int ballx=100;
private int bally=100;
public MySurfaceView(Context context){
super(context);
holder = getHolder();
holder.addCallback(this);
holder.setFixedSize(getWidth(), getHeight());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
screenWidth = w;
screenHeight = h;
thread = new Thread(this);
thread.start();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread=null;
}
@Override
public void run() {
while (thread != null) {
if (ballx-rectWidth < 0 || ballx + rectWidth > screenWidth)
dx = -dx;
if (bally-rectHeight < 0 || bally + rectHeight > screenHeight)
dy = -dy;
ballx += dx;
bally += dy;
doDraw(holder);
}
}
private void doDraw(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas();
if (canvas != null){
Paint paint = new Paint();
paint.setColor(Color.GREEN);
canvas.drawColor(Color.BLACK);
canvas.drawCircle(ballx, bally, 20, paint);
holder.unlockCanvasAndPost(canvas);
}
}
}
解説
Runメソッド内の
if (ballx-rectWidth < 0 || ballx + rectWidth > screenWidth)
dx = -dx;
if (bally-rectHeight < 0 || bally + rectHeight > screenHeight)
dy = -dy;
ballx += dx;
bally += dy;
にて円を描く位置を求めています。円のx,y座標が画面端を超える場合(0以下、または画面幅を超える)場合は符号を反転しふきを変えています。rectWidth, rectHeightはボールの大きさです。drawCircleに与える描画座標は円の中心の座標のため、rectWidthが0の場合、円の中心が画面端を超えるまで円の進行方向が反転しません。
実行結果
プロジェクトを実行します。円が動きます。画面端で円の進行方向が反転し跳ね返ったように見えます。
補足 - コールバックとスレッド描画を別クラスにして実装するコード
上記のコードは、SurfaceViewとコールバックを受けるクラスと、スレッドクラスを同一のクラスにして実装していますが、規模が大きくなった場合、それぞれ別クラスにしたい場合が出てきます。SurfaceView, コールバック, スレッドクラスを分けて実装したコードが以下になります。
MainActivity.java
MainActivityのクラスです。こちらは変更はありません。
package com.iPentec.simplesurfaceviewthread;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
setContentView(new MySurfaceView(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
MySurfaceView.java
SurfaceViewのクラスです。SurfaceHoloderを取得し、addCallback()メソッドを呼び出しコールバック先のクラス(MySurfaceViewCallback)を設定します。
package com.iPentec.simplesurfaceviewthread;
import android.content.*;
import android.view.*;
public class MySurfaceView extends SurfaceView {
//private Thread thread;
private SurfaceHolder holder;
public MySurfaceView(Context context){
super(context);
MySurfaceViewCallback msvc = new MySurfaceViewCallback();
holder = getHolder();
holder.addCallback(msvc);
holder.setFixedSize(getWidth(), getHeight());
}
}
MySurfaceViewCallback.java
SurfaceViewからのコールバックを受け取るクラスです。surfaceChangedイベントが発生するとき、画面描画用のスレッドを生成します。
package com.iPentec.simplesurfaceviewthread;
import android.view.SurfaceHolder;
public class MySurfaceViewCallback implements SurfaceHolder.Callback{
private Thread thread;
@Override
public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
MyDrawThread mdt = new MyDrawThread();
mdt.holder = holder;
mdt.screenWidth = w;
mdt.screenHeight = h;
thread = new Thread(mdt);
thread.start();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread=null;
}
}
MyDrawThread.java
スレッドクラスです。SurfaceHolderを用いてCanvasを取得し画面描画をします。
package com.iPentec.simplesurfaceviewthread;
import android.view.SurfaceHolder;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
public class MyDrawThread implements Runnable{
public SurfaceHolder holder;
private float dx = 5, dy = 5;
public float screenWidth, screenHeight;
private static final float rectWidth = 40;
private static final float rectHeight = 40;
private int ballx=100;
private int bally=100;
@Override
public void run() {
//while (thread != null) {
while (true) {
if (ballx-rectWidth < 0 || ballx + rectWidth > screenWidth)
dx = -dx;
if (bally-rectHeight < 0 || bally + rectHeight > screenHeight)
dy = -dy;
ballx += dx;
bally += dy;
doDraw(holder);
}
}
private void doDraw(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas();
if (canvas != null){
Paint paint = new Paint();
paint.setColor(Color.GREEN);
canvas.drawColor(Color.BLACK);
canvas.drawCircle(ballx, bally, 20, paint);
holder.unlockCanvasAndPost(canvas);
}
}
}
実行結果
実行結果は先のプログラムと同じになります。
著者
iPentecのプログラマー、最近はAIの積極的な活用にも取り組み中。
とっても恥ずかしがり。
最終更新日: 2024-01-04
作成日: 2013-03-19