2012年3月4日日曜日

[Android SDK] 描画の基礎

Androidの描画の基礎として、Canvas、Paintクラスの使い方をまとめました。

2012年3月2日金曜日

[Android SDK] 加速度センサで傾きを測定する

Android携帯には様々なセンサが搭載されています。
機種によってまちまちですが、その中でも加速度センサを利用して、Android携帯の傾きを測定してみます。

まずは、SensorManagerという、センサ関連を統括して管理しているサービスを取得します。
SensorManager manager = (SensorManager)getSystemService(SENSOR_SERVICE);

続いて、センサの状態が変化したときに呼ばれるSensorEventListenerというリスナーを作成します。
サブクラスに実装するか、実装してインスタンスを作成してください。
SensorEventListener lis = new SensorEventListener() {
    // センサの精度が変化したときに呼ばれる
    public void onAccuracyChanged(Sensor sensor, int accuracy) {}
    // センサの値が変化したときに呼ばれる
    public void onSensorChanged(SensorEvent event) {}
};

試しに、Activityの中に加速度センサで取得した値を表示するサンプルを作成してみました。
/* フィールド変数 */
SensorManager mSensorManager;

/* 初期化処理メソッド */ {
 // センサーマネージャを取得
 mSensorManager = (SensorManager) c.getSystemService(Context.SENSOR_SERVICE);
 List sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
 if (sensors != null && sensors.size() > 0) {
  Sensor s = sensors.get(0);
  mSensorManager.registerListener(mSensorListener, s, SensorManager.SENSOR_DELAY_UI);
 }
}
/* 終了処理メソッド */ {
 // センサーマネージャを破棄
 mSensorManager.unregisterListener(mSensorListener);
 mSensorManager = null;
}
SensorEventListener mSensorListener = new SensorEventListener() {
 public void onAccuracyChanged(Sensor sensor, int accuracy) {
  Log.d(TAG, sensor.getName()+":"+accuracy);
 }
 public void onSensorChanged(SensorEvent event) {
  if (event.values.length == 3) {
   Log.d(TAG, event.sensor.getName()+" ("+event.values[0]+","+event.values[1]+","+event.values[2]+")");
  }
 }
}

解説すると、まずは加速度センサのオブジェクトを取得するために、引数にSensor.TYPE_ACCELEROMETERを指定します。
List sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);

Android携帯によっては同一センサが複数搭載しているケースがあるため、Listオブジェクトが返り値となっている。
とりあえず、加速度センサが複数ついていないハズなので、要素0番目を取得してリスナーに登録する。
if (sensors != null && sensors.size() > 0) {
 Sensor s = sensors.get(0);
 mSensorManager.registerListener(mSensorListener, s, SensorManager.SENSOR_DELAY_UI);
}
ここでの3番目の引数は、センサー状態を通知する際の遅延時間です。
加速度センサの使用用途に合わせて、早すぎず遅すぎず適切なものを選ぶと良い。
定数の名前からも分かるように用途が分かりますね。
SensorManager.SENSOR_DELAY_FASTEST:0ms
SensorManager.SENSOR_DELAY_GAME:20ms
SensorManager.SENSOR_DELAY_UI:60ms
SensorManager.SENSOR_DELAY_NORMAL:200ms

肝心なSensorEventの中身ですが、重要なのはvaluesの値です。
加速度センサの場合はAndroid携帯を正面に持った状態で
values[0] : X軸 左右(左+、右-)
values[1] : Y軸 上下(下+、上-)
values[2] : Z軸 前後(後ろ側+、手前-)

という風な加速度センサの値が入っている。

常に重力分(9.8m/s'2=約10.0)が働いているため、静止状態でベクトルの大きさを計算すると10.0となる。
要するに、平らな机の置くと、後ろ側方向にのみ重力がかかるので、
・values[0]:0.0
・values[1]:0.0
・values[2]:10.0
となる。

試しにやってみるのが一番です。

最後にActivity破棄されるときにはリスナーも登録破棄してくださいね。
mSensorManager.unregisterListener(mSensorListener);






2012年3月1日木曜日

[Android SDK] SurfaceViewを使った高速描画

SurfaceViewは、描画用のスレッドがアプリケーションのスレッドと独立しているため、ゲームなど定期的な描画が要求される場合に向いている。
なお、SurfaceViewはAPI Level1から利用できる。

試しに、適当なサンプルを作成してみる。
1.Activityは最初に生成されるものから変更なし
package com.runpeta.android.angle;

import android.app.Activity;
import android.os.Bundle;

public class RpAngleActivity extends Activity {
 // アクティビティ作成時に最初に呼ばれる
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

2.SurfaceViewのサブクラスを作り、線だけ描画する
package com.runpeta.android.angle;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class RpAngleSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
 public RpAngleSurfaceView(Context context) {
  super(context);
  getHolder().addCallback(this);
 }
 public RpAngleSurfaceView(Context context, AttributeSet attrs) {
  super(context, attrs);
  getHolder().addCallback(this);
 }
 public RpAngleSurfaceView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  getHolder().addCallback(this);
 }
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  
 }
 public void surfaceCreated(SurfaceHolder holder) {
  doDraw();
 }
 public void surfaceDestroyed(SurfaceHolder holder) {
  
 }
 private void doDraw() {
     // キャンバスをロックする
     Canvas c = getHolder().lockCanvas();
     c.save();
     
     // ここにCanvasに描画処理を書く
     Paint p = new Paint();
     p.setColor(Color.BLACK);
     c.drawColor(Color.WHITE);
     c.drawLines(new float[]{20,20,60,60}, p);
     
     // キャンバスのロックを解除する
     c.restore();
     getHolder().unlockCanvasAndPost(c);
    }
}

3.メインレイアウトに作成したSurfaceViewを追加します。
・id:surface (とりあえず)
・width:fill_parent
・height:fill_parent

これで線なるものが描画されているハズだ。


これは非常に分かりづらいサンプルになってしまった。

ゲームのように定期的に再描画を行ってみる。

さきほどの"onDraw"をタイマーで定期的に呼び出すために、"ScheduledExecutorService"を利用します。
こんな感じにしようできますので、(定期処理内容)で"onDraw"を呼びます。
// タイマーを開始する
ScheduledExecutorService schedule;
schedule = Executors.newSingleThreadScheduledExecutor();
schedule.scheduleAtFixedRate(new Runnable(){
    public void run() {
        //定期処理内容

    }
}, 0, 100, TimeUnit.MILLISECONDS);

// タイマーを終了する
schedule.shutdown();

さきほどの線がアニメーションで伸び縮みするようにしてみます。
package com.runpeta.android.angle;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class RpAngleSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
 private ScheduledExecutorService mSchedule;
 private Runnable repeatDraw = new Runnable() {
  public void run() {
   size += 5;
   if (size > 400) {
    size = 100;
   }
   doDraw();
  }
 };
 private int size = 100;
 public RpAngleSurfaceView(Context context) {
  super(context);
  getHolder().addCallback(this);
 }
 public RpAngleSurfaceView(Context context, AttributeSet attrs) {
  super(context, attrs);
  getHolder().addCallback(this);
 }
 public RpAngleSurfaceView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  getHolder().addCallback(this);
 }
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  
 }
 public void surfaceCreated(SurfaceHolder holder) {
  doDraw();
  mSchedule = Executors.newSingleThreadScheduledExecutor();
  mSchedule.scheduleAtFixedRate(repeatDraw, 0, 100, TimeUnit.MILLISECONDS);
 }
 public void surfaceDestroyed(SurfaceHolder holder) {
  mSchedule.shutdown();
  mSchedule = null;
 }
 private void doDraw() {
     // キャンバスをロックする
     Canvas c = getHolder().lockCanvas();
     c.save();
     
     // ここにCanvasに描画処理を書く
     Paint p = new Paint();
     p.setColor(Color.BLACK);
     c.drawColor(Color.WHITE);
     c.drawLines(new float[]{20,20,size,size}, p);
     
     // キャンバスのロックを解除する
     c.restore();
     getHolder().unlockCanvasAndPost(c);
    }
}

こんな感じで描画ができることが分かったので、後はCanvasとPaintの使い方次第でいろいろ描画できます。