感測器 Sensor 加速度【示例】

weixin_34377065發表於2016-10-28

簡介
座標系

x軸:從左到右
y軸:從下到上
z軸:從內到外
這個座標系與Android 2D API中的不同,感測器中的返回值都以此座標系為準。

SENSOR_TYPE_ACCELEROMETER       1 //加速度
SENSOR_TYPE_MAGNETIC_FIELD      2 //磁力
SENSOR_TYPE_ORIENTATION         3 //方向
SENSOR_TYPE_GYROSCOPE           4 //陀螺儀
SENSOR_TYPE_LIGHT               5 //光線感應
SENSOR_TYPE_PRESSURE            6 //壓力
SENSOR_TYPE_TEMPERATURE         7 //溫度
SENSOR_TYPE_PROXIMITY           8 //接近
SENSOR_TYPE_GRAVITY             9 //重力
SENSOR_TYPE_LINEAR_ACCELERATION 10//線性加速度
SENSOR_TYPE_ROTATION_VECTOR     11//旋轉向量

API概況
sensor相關API被放到了android.hardware包下,主要使用的類有Sensor、SensorEvent、SensorManager及SensorEventListener介面。
SensorManager順其自然的擔任起管理的工作,負責註冊監聽某Sensor的狀態;Sensor的資料通過SensorEvent返回。
  • Sensor: 表示感測器的類,它儲存有感測器名稱,廠商,版本,精確度等資訊
  • SensorEvent:表示感測器事件,它可以儲存感測器的值,感測器型別,時間戳等資訊
  • SensorEventListener:用於接收感測器來自SensorManager的通知,當感測器發生變化時,它包含兩個回撥函式
  • SensorManager:SensorManager讓你可以訪問手機的全部感測器
  • SensorListener:已廢除
注意:應當始終保證在不需要使用感測器的時候禁用感測器,特別是當你的activity【暫停】的時候。沒有這樣做將會導致電池只能使用很少幾個小時。記住,系統不會在螢幕關閉的時候自動禁用感測器。

延遲時間的精密度引數如下:
SensorManager.SENSOR_DELAY_FASTEST     0ms
SensorManager.SENSOR_DELAY_GAME        20ms
SensorManager.SENSOR_DELAY_UI              60ms
SensorManager.SENSOR_DELAY_NORMAL   200ms
因為感應檢測Sensor的服務是否頻繁和快慢都與電池參量的消耗有關,同時也會影響處理的效率,所以兼顧到消耗電池和處理效率的平衡,需要根據應用系統的需求來做適當的設定。

加速度
加速度感測器的背景
這裡的加速度特指重力加速度,所以在【靜止時】重力感測器的返回值與加速度感測器值相同。
地表上靜止物體的重力加速度約為9.8 m/s^2.
借用SensorManager中的常量:
public static final float STANDARD_GRAVITY = 9.80665F;

我們可以藉助三軸上的值來確定裝置的狀態,比如:
  • 將手機平放在桌面上,x軸預設為0,y軸預設0,z軸預設9.81。
  • 將手機朝下放在桌面上,z軸為-9.81。
  • 將手機向左傾斜,x軸為正值;當x軸的值接近重力加速度時,說明裝置的左邊朝下。
  • 將手機向右傾斜,x軸為負值;當x軸的值接近負的g值時,說明裝置的右邊朝下。
  • 將手機向上傾斜,y軸為負值;當y軸的值接近負的g值時,說明裝置的上邊朝下。
  • 將手機向下傾斜,y軸為正值;當y軸的值接近g值時,說明裝置的下邊超下。

地磁
磁場感測器主要讀取的是磁場的變化,通過該感測器便可開發出指南針、羅盤等磁場應用。
該感測器讀取的資料同樣是空間座標系三個方向的磁場值,其資料單位為T。
磁場感測器可以用來檢測磁場大小,和加速度感測器一樣,有x、y、z軸三個方向,單位為uT(microteslas),即微特斯拉
磁場感測器也稱為compass(指南針),在uses-feature中使用Android.hardware.sensor.compass作為其名字。
可以拿著手機到處測測,在電器附近不同位置,值還是相差巨大的。
不過單看磁場數值其實也看不出所以然。

方向
安卓平臺提供了2個感測器用於讓我們判斷裝置的位置,分別是【地磁場感測器】和【方向感測器】。關於Orientation Sensor在官方文件中的概述裡有這樣一句話:
The orientation sensor is software-based and derives its data from the accelerometer and the geomagnetic field sensor. 方向感測器是基於軟體的,並且它的資料是通過【加速度感測器】和【磁場感測器】共同獲得的。

  • 第一個元素azimuth,【z軸旋轉角度】,手機由水平正北放置時開始順時針旋轉,z的值變化情況為0~360/0;表示指向地心的【方位角】
  • 第二個元素pitch,【x軸旋轉角度】,手機由水平正北放置時開始順時針旋轉,x的值變化情況為0~-180/180~0;表示前後旋轉的【仰俯角】
  • 第三個元素roll,【y軸旋轉角度】,手機由水平正北放置時開始順時針旋轉,y的值變化情況為0~90~0~-90~0;表示左右旋轉的【翻轉角】
一定要清楚,上面的值都是【旋轉】角度,上面的總結是沒有錯的,如果你覺得錯了,那就是沒有理解【旋轉】的意思。
當手機頂部指向正北方時,方向值為0;頂部指向正東方時,方向值為90;頂部指向正南方時,方向值為180;頂部指向正西方時,方向值為270。

磁場+加速度代替方向感測器
在最新版的SDK中,使用Orientation感測器會看到這麼一句話“This constant is deprecated. use SensorManager.getOrientation() instead. ”
即這種方式已過期,不建議使用!Google建議我們在應用程式中使用SensorManager.getOrientation()來獲得原始資料。
public static float[] getOrientation (float[] R, float[] values)
  • 第一個引數是R[] 是一個旋轉矩陣,用來儲存磁場和加速度的資料,可以理解為這個函式的傳入值,通過它這個函式給你求出方位角。
  • 第二個引數就是這個函式的輸出了,他有函式自動為我們填充,這就是我們想要的。
輸出值values各個元素的含義
  • values[0]  :方向角,但用(磁場+加速度)得到的資料範圍是(-180~180),也就是說,0表示正北,90表示正東,180/-180表示正南,-90表示正西。而直接通過方向感應器資料範圍是(0~359360/0表示正北,90表示正東,180表示正南,270表示正西
  • values[1]  pitch 傾斜角,即由靜止狀態開始,前後翻轉,手機頂部往上抬起(0~-90),手機尾部往上抬起(0~90)
  • values[2]  roll 旋轉角,即由靜止狀態開始,左右翻轉,手機左側抬起(0~90),手機右側抬起(0~-90)
現在問題是這個R[]怎麼獲取,其實他是通過函式getRotationMatrix得到的。
看看getRotationMatrix的定義:
public static boolean getRotationMatrix (float[] R, float[] I, float[] gravity, float[] geomagnetic)
  • 第一個就是我們需要填充的R陣列,大小是9
  • 第二個是一個轉換矩陣,將磁場資料轉換進實際的重力座標中,一般預設情況下可以設定為null
  • 第三個是一個大小為3的陣列,表示從加速度感應器獲取來的資料,在onSensorChanged中
  • 第四個是一個大小為3的陣列,表示從磁場感應器獲取來的資料,在onSensorChanged中

加速度示例
public class AccelerometerActivity extends ListActivity implements SensorEventListener {
    private TextView tv_info;
    private SensorManager sm;//感測器管理器
    private Vibrator vibrator;//震動
    private long lastTime = System.currentTimeMillis();
    private static final int UPTATE_INTERVAL_TIME = 3500;// 兩次檢測的時間間隔  
    private static final float MEDUMVALUE = SensorManager.STANDARD_GRAVITY + 8.5f;//標準值為9.80665
    private static final float SENSEVALUE = SensorManager.STANDARD_GRAVITY - 0.5f;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = { "註冊,搖一搖,檢測手機螢幕方向""取消註冊"};
        tv_info = new TextView(this);
        tv_info.setTextColor(Color.BLUE);
        tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
        tv_info.setPadding(20, 10, 20, 10);
        getListView().addFooterView(tv_info);
        setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1new ArrayList<String>(Arrays.asList(array))));
        sm = (SensorManager) getSystemService(SENSOR_SERVICE);
        vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);//許可權【android.permission.VIBRATE】
    }
    protected void onResume() {
        super.onResume();
        if (sm != null) sm.registerListener(thissm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
    }
    protected void onPause() {//保證在不需要使用感測器的時候禁用感測器
        super.onPause();
        if (sm != null) sm.unregisterListener(this);
    }
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        switch (position) {
        case 0:
            if (sm != null) sm.registerListener(thissm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);//設定獲取感測器資訊的頻率
            break;
        case 1://Accelerometer 加速度感測器,32
            if (sm != null) sm.unregisterListener(this);
            break;
        }
    }
    @Override
    public void onSensorChanged(SensorEvent event) {//在感應檢測到Sensor的值有變化時會被呼叫到
        //實時檢測,震動
        if (Math.abs(event.values[0]) > MEDUMVALUE || Math.abs(event.values[1]) > MEDUMVALUE || Math.abs(event.values[2]) > MEDUMVALUE) vibrator.vibrate(200);
        //抽樣檢測
        if (System.currentTimeMillis() - lastTime < UPTATE_INTERVAL_TIME) return;
        lastTime = System.currentTimeMillis();// 現在的時間變成last時間
        tv_info.setText("感測器型別 " + event.sensor.getName() + "\n時間戳 " + event.timestamp + "\n精度 " + event.accuracy + //
                "\nx軸方向的值,右側向上時為正 " + event.values[0] + "\ny軸方向的值,前側向上時為正 " + event.values[1] + "\nz軸方向的值,螢幕向上時為正 " + event.values[2]);
        //檢測手機螢幕方向
        if (event.values[0] > SENSEVALUE) Toast.makeText(this"螢幕朝左,重力指向裝置左邊", Toast.LENGTH_SHORT).show();
        else if (event.values[0] < -SENSEVALUE) Toast.makeText(this"螢幕朝右,重力指向裝置右邊", Toast.LENGTH_SHORT).show();
        else if (event.values[1] > SENSEVALUE) Toast.makeText(this"螢幕朝前,重力指向裝置下邊", Toast.LENGTH_SHORT).show();
        else if (event.values[1] < -SENSEVALUE) Toast.makeText(this"螢幕朝後,重力指向裝置上邊", Toast.LENGTH_SHORT).show();
        else if (event.values[2] > SENSEVALUE) Toast.makeText(this"螢幕朝上", Toast.LENGTH_SHORT).show();
        else if (event.values[2] < -SENSEVALUE) Toast.makeText(this"螢幕朝下", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onAccuracyChanged(Sensor paramSensor, int paramInt) {//在感應檢測到Sensor的精密度有變化時被呼叫到
    }
}

方向示例
public class OrientationActivity2 extends Activity implements SensorEventListener {
    private TextView tv_info;
    private TextView tv_orientation;
    private ImageView iv;
    private SensorManager sm;//感測器管理器
    private float[] accelValues = new float[3];
    private float[] magValues = new float[3];
    private long lastTime = System.currentTimeMillis();
    private static final int UPTATE_INTERVAL_TIME = 500;// 兩次檢測的時間間隔  
    private float lastRotateDegree;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_info = (TextView) findViewById(R.id.tv_info);
        tv_orientation = (TextView) findViewById(R.id.tv_orientation);
        iv = (ImageView) findViewById(R.id.iv);
        sm = (SensorManager) getSystemService(SENSOR_SERVICE);
    }
    protected void onResume() {
        super.onResume();
        if (sm != null) {
            sm.registerListener(thissm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_NORMAL);
            sm.registerListener(thissm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
        }
    }
    protected void onPause() {//保證在不需要使用感測器的時候禁用感測器
        super.onPause();
        if (sm != null) sm.unregisterListener(this);
    }
    @Override
    public void onSensorChanged(SensorEvent event) {
        switch (event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            accelValues = event.values;
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            magValues = event.values;
            break;
        }
        tv_info.setText("感測器型別 " + event.sensor.getName() + "\n獲取到的值\n" + event.values[0] + "\n" + event.values[1] + "\n" + event.values[2]);
        if (System.currentTimeMillis() - lastTime < UPTATE_INTERVAL_TIME) return;
        lastTime = System.currentTimeMillis();// 現在的時間變成last時間
        calculateOrientation();
    }
    @Override
    public void onAccuracyChanged(Sensor paramSensor, int paramInt) {
    }

    private void calculateOrientation() {
        float[] R = new float[9];//旋轉陣列
        float[] values = new float[3];//模擬方向感測器的資料
        //要填充的旋轉陣列;將磁場資料轉換進實際的重力座標中,一般預設情況下可以設定為null;加速度感測器資料;地磁感測器資料 
        SensorManager.getRotationMatrix(R, nullaccelValuesmagValues);
        SensorManager.getOrientation(R, values);
        //將弧度轉化為角度後輸出  
        tv_orientation.setText("角度\n");
        for (float value : values) {
            value = (float) Math.toDegrees(value);
            tv_orientation.append(value + "\n");
        }
        float value = -(float) Math.toDegrees(values[0]);
        if (Math.abs(value - lastRotateDegree) > 1) {
            //旋轉補間動畫
            RotateAnimation animation = new RotateAnimation(lastRotateDegree, value, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            animation.setFillAfter(true);
            iv.startAnimation(animation);
            lastRotateDegree = value;
        }
        value = -value;
        if (value >= -10 && value < 10) {
            tv_orientation.append("正北");
        } else if (value >= 10 && value < 80) {
            tv_orientation.append("東北");
        } else if (value >= 80 && value <= 100) {
            tv_orientation.append("正東");
        } else if (value >= 100 && value < 170) {
            tv_orientation.append("東南");
        } else if ((value >= 170 && value <= 180) || (value) >= -180 && value < -170) {
            tv_orientation.append("正南");
        } else if (value >= -170 && value < -100) {
            tv_orientation.append("西南");
        } else if (value >= -100 && value < -80) {
            tv_orientation.append("正西");
        } else if (value >= -80 && value < -10) {
            tv_orientation.append("西北");
        }
    }
}





附件列表

 

相關文章