端末の向きと傾きを取得する方法 - 加速度センサーと地磁気センサーの利用
Android や iPhone などの端末を始めて使った人が驚くことのひとつは、 ビー玉のような玉を、端末を傾けることによって操作して迷路から抜けるようなゲームをみたときではないでしょうか。
端末を傾けるとビー玉が転がり落ちるように動くので、まるで本物の玉のようです。
そのようなプログラムは当然ながら、端末が端末の傾きなどを感じ取るセンサー(ハードウェア)を内臓していなければできないことです。
それでは、センサーがある場合はそれをプログラム(ソフトウェア)からはどのように使えばよいでしょうか。
この資料では、加速度センサーと地磁気センサーを組み合わせて利用することによって、Android 端末の向きや傾きを知る方法を説明します。
傾きと向きを表す座標
傾きは方向はどのように表現したらよいでしょうか。
Android では端末上側を y 軸、右向きを x 軸、画面から外に向かう方向を z 軸にします。 このときに、x 軸回りの回転をピッチ (Pitch)、y 軸回りをロール (Roll) 、z 軸回りをアジマス (Azimuth) といいます。
雑な図ですがピッチとロールはこんな感じ・・・
飛行機に見立てたときに、機首の上げ下げがピッチで、主翼の先端を上下するのがロールです。飛行機が飛んでいる方角がアジマスで表されます。
利用するセンサー
傾きは加速度センサーで検出でき、方角は地磁気センサーでわかりますので、これらをうまいこと組み合わせると端末の向きと傾きを取得できる、 というわけです。
加速度センサーと地磁気センサーを利用するコード
では具体的に加速度センサーと地磁気センサーを利用するために、どうしたらよいでしょうか。
センサーマネージャを用いて Sensor.TYPE_ACCELEROMETER と Sensor.TYPE_MAGNETIC_FIELD にリスナーを登録することで、 それぞれ加速度センサー、地磁気センサーの変化を受け取れます。
センサーのイベントリスナーとなるためには、SensorEventListener を実装する必要があります。
SensorEventListener のメソッドである onSensorChanged にて下のプログラム例のようにすることで、それぞれの角度が計算可能です。
角度を出力するだけのサンプルプログラム
それでは実際に傾きと向きを表示するプログラムを作りましょう。
このスクリーンショットは角度を表示しています。
コードは次の通り。
package com.keicode.android.test;
import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;
public class SensorTest2 extends Activity
implements SensorEventListener {
public final static String TAG = "SensorTest2";
protected final static double RAD2DEG = 180/Math.PI;
SensorManager sensorManager;
float[] rotationMatrix = new float[9];
float[] gravity = new float[3];
float[] geomagnetic = new float[3];
float[] attitude = new float[3];
TextView azimuthText;
TextView pitchText;
TextView rollText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViews();
initSensor();
}
public void onResume(){
super.onResume();
sensorManager.registerListener(
this,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(
this,
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_GAME);
}
public void onPause(){
super.onPause();
sensorManager.unregisterListener(this);
}
protected void findViews(){
azimuthText = (TextView)findViewById(R.id.azimuth);
pitchText = (TextView)findViewById(R.id.pitch);
rollText = (TextView)findViewById(R.id.roll);
}
protected void initSensor(){
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
public void onSensorChanged(SensorEvent event) {
switch(event.sensor.getType()){
case Sensor.TYPE_MAGNETIC_FIELD:
geomagnetic = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
gravity = event.values.clone();
break;
}
if(geomagnetic != null && gravity != null){
SensorManager.getRotationMatrix(
rotationMatrix, null,
gravity, geomagnetic);
SensorManager.getOrientation(
rotationMatrix,
attitude);
azimuthText.setText(Integer.toString(
(int)(attitude[0] * RAD2DEG)));
pitchText.setText(Integer.toString(
(int)(attitude[1] * RAD2DEG)));
rollText.setText(Integer.toString(
(int)(attitude[2] * RAD2DEG)));
}
}
}