簡介
座標系
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~359)360/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_1, new 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(this, sm.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(this, sm.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(this, sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_NORMAL);
sm.registerListener(this, sm.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, null, accelValues, magValues);
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("西北");
}
}
}附件列表