重力センサーを使ったアプリの基本 2020/08/07
 重力センサーを使ったアプリを作りました。アンドロイドでは加速度センサーという似たようなセンサーがありあす。違いは、データを取り込む速度の違いです。重力加速度が激しく変化するようなことはありませんから、データを取り込む速度は遅くても問題ありません。飛んだり跳ねたりする激しい運動などでは取り込む速度が速くないとデータを取りこぼします。このようなときは加速度センサーを使ってみてください。実際のアプリはこれ。

X,Y,Zのデータと重力加速度が一番下に表示されています。ところで重力加速度って何?高校の物理でやったのですがわからない人が多いのかも。

1G = 9.80665 m/s2


です。

上図においてセンサーから取得するデータはGravityX, GravityY,GravityZです。これらのセンサーデータから加速度を求める式は、

です。計算結果は9.81になっていてお見事です。Android 2.1のころ加速度センサーのアプリを作ったのですが、こんなに見事ではなかったです。加速度センサーの記事を少し見ましたが、この式を使ってなくて、例えばgraviyXだけを使って何やら変な計算でシェークを検出なんてやってます。
歩数計などに応用することを考えてみます。加速度センサーは素早くデータを出力して十分な精度が出るように思います。重力センサーでは飛んでも跳ねても9.81で変わりません。センサーの出力データに何か補正でも入っているような気がしいます。重力センサーでちょっとしたことで数値がばらつくと不信に思われるのが嫌なのか?

実機を使ってスマホの向きとセンサーのデータがどうなっているか調べてみます。

スマホを机に置いたときは、Zが9.8に近くなっています。X,Yはほぼ0です。
次にまっすぐに立ててみます。

Yが9.8で、XYはほぼ0です。
反対に立てると、

Yの値がマイナスです。
コントは横に立ててみます。

Xが9.6でYZが小さな値です。このとき垂直にうまく建てられませんでした。
逆に立ててみます。

Xがマイナスになりました。
何かわかっていただければ幸いです。

アプリの作り方
アプリの概要は、次のようになります。


DataView.javaについて
●Viewから派生したDataViewを作ります。
●コンストラクタDataView(Context cntext)引数のcntextはMainActivityなのでこれを取り込みます。
●onDraw(Canvas canvas)で描画します。
センサーデータはMainActivityが所有しているので、m_MainActivity.getGravityX();などを使って取得します。取得したデータは角まるめ四角形ないでセンタリングをして表示します。

package net.sadaji.gravity;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;

public class DataView extends View {
    private MainActivity m_MainActivity;
    //constractor
    DataView(Context cntext){
        super(cntext);
        m_MainActivity = (MainActivity)cntext;
        setBackgroundColor(Color.GREEN);
    }

    @Override
    protected void onDraw(Canvas canvas){
        //String  str;
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        drawData(canvas, paint, 0,  "GravityX", m_MainActivity.getGravityX());
        drawData(canvas, paint, 1,  "GravityY", m_MainActivity.getGravityY());
        drawData(canvas, paint, 2,  "GravityZ", m_MainActivity.getGravityZ());
        drawData(canvas, paint, 3,  "Gravity", m_MainActivity.getGravity());

    }

    private void drawData(Canvas canvas, Paint paint, int Pos, String dataName, double data){
        String str = String.format("%s:%.2f", dataName, data);
        int width = getWidth();
        RectF rc = new RectF();
        int padding = 60;
        rc.left = padding;
        rc.top = 120 + Pos * 180;
        rc.right = width  - padding;
        rc.bottom = rc.top + 140;
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.rgb(255, 255, 0));
        canvas.drawRoundRect(rc, 60, 60, paint);

        paint.setTextSize(98);
        paint.setColor(Color.rgb(0, 0, 0));
        float textWidth = paint.measureText(str);
        int x = (int)(rc.left + ((rc.right  - rc.left) - textWidth) / 2);
        Paint.FontMetrics metrics = new Paint.FontMetrics();
        float textHeight = paint.getFontMetrics(metrics);
        int y = (int)(rc.bottom - ((rc.bottom  - rc.top) - textHeight) / 2 );
        y -= metrics.descent;
        canvas.drawText(str, x, y, paint);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(rc, 60, 60, paint);
    }
}

MainActivity.javaについて
●EmptyViewを使います。
●onCreate()
DataViewを作成します。
m_dataView = new DataView(this);
m_dataViewをセットします。
setContentView(m_dataView);

センサーのイベントリスナーの登録
onSensorChanged()で_dataView に再描画をさせる

package net.sadaji.gravity;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.Rect;
import android.os.Bundle;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.Window;
import android.widget.Toast;
import java.lang.Math;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    private SensorManager manager;
    private SensorEventListener listener;
    private List list;
    double g_X, g_Y, g_Z, grv;
    DataView m_dataView;
    int g_statusBarHeight;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        m_dataView = new DataView(this);
        setContentView(m_dataView);
        manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
 //       list = manager.getSensorList(Sensor.TYPE_GRAVITY);
        list = manager.getSensorList(Sensor.TYPE_ACCELEROMETER);

        listener = new SensorEventListener() {
            public void onSensorChanged(SensorEvent event) {
                g_X = (double)event.values[0];
                g_Y = (double)event.values[1];
                g_Z = (double)event.values[2];
                grv = Math.sqrt(g_X * g_X + g_Y * g_Y + g_Z *g_Z);
                m_dataView.invalidate();

            }

            public void onAccuracyChanged(Sensor sensor, int accuracy) {}
        };

 //       requestWindowFeature(Window.FEATURE_NO_TITLE);
    }
    @Override
    protected void onResume() {
        super.onResume();
        if (list.size() > 0) {
            manager.registerListener(listener, list.get(0),
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        manager.unregisterListener(listener, list.get(0));
    }

    public double getGravityX()
    {
        return g_X;
    }
    public double getGravityY()
    {
        return g_Y;
    }
    public double getGravityZ()
    {
        return g_Z;
    }
    public double getGravity()
    {
        return grv;
    }

}