获取Android设备的方向 ,使用加速度重力传感器
帶有g-sensor的Android設備上可通過API獲取到設備的運動加速度,應用程序通過一些假設和運算,可以從加速度計算出設備的方向
獲取設備運動加速度的基本代碼是:
SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);sm.registerListener(new SensorEventListener() {public void onSensorChanged(SensorEvent event) {if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {return;}float[] values = event.values;float ax = values[0];float ay = values[1];float az = values[2];// TODO Have fun with the acceleration components... }public void onAccuracyChanged(Sensor sensor, int accuracy) {}}, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);SendorEventListener 通過 SendorEvent 回調參數獲得當前設備在坐標系x、y、z軸上的加速度分量。SensorEvent 的 api doc 中定義了這里使用的坐標系為:
我暫且稱之為“設備坐標系”吧,設備坐標系是固定于設備的,與設備的方向(在世界坐標系中的朝向)無關
精確地說,Sensor Event 所提供的加速度數值,是設備以地球為參照物的加速度減去重力加速度的疊加后的值。我是這樣理解的:當以重力加速度g向地面作自由落體運動時,手機處于失重狀態,g-sensor以這種狀態作為加速度的0;而當手機處于靜止狀態(相對于地面)時,為了抵御自由落體運動的趨勢,它有一個反向(向上)的g的加速度。因此,得出一個結論:當設備處于靜止或者勻速運動狀態時,它有一個垂直地面向上的g的加速度,這個g投影到設備坐標系的x、y、z軸上,就是SensorEvent 提供給我們的3個分量的數值。在“設備處于靜止或者勻速運動狀態”的假設的前提下,可以根據SensorEvent所提供的3個加速度分量計算出設備相對于地面的方向
前面所提到的“設備的方向”是一個含糊的說法。這里我們精確地描述設備方向為:以垂直于地面的方向為正方向,用設備坐標系x、y、z軸與正方向軸之間的夾角Ax、Ay、Az來描述設備的方向,如下圖所示。可以看出,設備還有一個自由度,即:繞著正方向軸旋轉,Ax、Ay、Az不變。但Ax、Ay、Az的約束條件,對于描述設備相對于正方向軸的相對位置已經足夠了。如果需要完全約束設備相對于地面的位置,除了正方向軸外,還需要引入另一個參照軸,例如連接地球南、北極的地軸(如果設備上有地磁強度Sensor,則可滿足該約束條件)
Ax、Ay、Az的范圍為[0, 2*PI)。例如,當Ay=0時,手機y軸豎直向上;Ay=PI時,手機y軸向下;Ay=PI/2時,手機水平、屏幕向上;Ay=3*PI/2時,手機水平、屏幕向下
根據3D矢量代數的法則,可知:
- Gx=g*cos(Ax)
 - Gy=g*cos(Ay)
 - Gz=g*cos(Az)
 - g^2=Gz^2+Gy^2+Gz^2
 
因此,根據Gx、Gy、Gz,可以計算出Ax、Ay、Az
在x-y平面上的2D簡化
當Ax、Ay確定時,Az有兩種可能的值,二者相差PI,確定了設備屏幕的朝向是向上還是向下。大多數情況下,我們只關心Ax、Ay(因為程序UI位于x-y平面?),而忽略Az,例如,Android的屏幕自動旋轉功能,不管使用者是低著頭看屏幕(屏幕朝上)、還是躺在床上看(屏幕朝下),UI始終是底邊最接近地心的方向
那么我們設Gx與Gy的矢量和為g'(即:g在x-y平面上的投影),將計算簡化到x-y 2D平面上。記y軸相對于g'的偏角為A,以A來描述設備的方向。以逆時針方向為正,A的范圍為[0, 2*PI)
有:
- g'^2=Gx^2+Gy^2
 - Gy=g'*cos(A)
 - Gx=g'*sin(A)
 
則:
- g'=sqrt(Gx^2+Gy^2)
 - A=arccos(Gy/g')
 
由于arccos函數值范圍為[0, PI];而A>PI時,Gx=g'*sin(A)<0,因此,根據Gx的符號分別求A的值為:
- 當Gx>=0時,A=arccos(Gy/g')
 - 當Gx<0時,A=2*PI-arccos(Gy/g')
 
注意:由于cos函數曲線關于直線x=n*PI 對稱,因此arccos函數的曲線如果在y軸方向[0, 2*PI]范圍內補全的話,則關于直線y=PI對稱,因此有上面當Gx<0時的算法
考慮應用程序的屏幕旋轉
前面計算出了Android設備的“物理屏幕”相對于地面的旋轉角度,而應用程序的UI又相對于“物理屏幕”存在0、90、180、270度4種可能的旋轉角度,要綜合考慮進來。也就是說:
- UI相對于地面的旋轉角度=物理屏幕相對于地面的旋轉角度-UI相對于物理屏幕的旋轉角度
 
Android應用獲取屏幕旋轉角度的方法為:
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();int degree= 90 * rotation;float rad = (float)Math.PI / 2 * rotation;Demo
根據上面的算法,我寫了一個“不倒翁”的Demo,當設備旋轉時,不倒翁始終是站立的。軟件市場上不少“水平尺”一類的應用,其實現原理應該是與此相同的
下載Demo源代碼
Activity實現了SensorEventListener,并且注冊到SensorManager。同時設置屏幕方向固定為LANDSCAPE:
private GSensitiveView gsView;private SensorManager sm;@Overridepublic void onCreate(Bundle savedInstanceState) {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);super.onCreate(savedInstanceState);gsView = new GSensitiveView(this);setContentView(gsView);sm = (SensorManager) getSystemService(SENSOR_SERVICE);sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);}@Overrideprotected void onDestroy() {sm.unregisterListener(this);super.onDestroy();}當g-sensor數據變化時的回調如下。這里就是根據我們前面推論的算法計算出UI旋轉的角度,并且調用GSensitiveView.setRotation()方法通知View更新
public void onSensorChanged(SensorEvent event) {if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {return;}float[] values = event.values;float ax = values[0];float ay = values[1];double g = Math.sqrt(ax * ax + ay * ay);double cos = ay / g;if (cos > 1) {cos = 1;} else if (cos < -1) {cos = -1;}double rad = Math.acos(cos);if (ax < 0) {rad = 2 * Math.PI - rad;}int uiRot = getWindowManager().getDefaultDisplay().getRotation();double uiRad = Math.PI / 2 * uiRot;rad -= uiRad;gsView.setRotation(rad);}GSensitiveView是擴展ImageView的自定義類,主要是根據旋轉角度繪制圖片:
private static class GSensitiveView extends ImageView {private Bitmap image;private double rotation;private Paint paint;public GSensitiveView(Context context) {super(context);BitmapDrawable drawble = (BitmapDrawable) context.getResources().getDrawable(R.drawable.budaow);image = drawble.getBitmap();paint = new Paint();}@Overrideprotected void onDraw(Canvas canvas) {// super.onDraw(canvas); double w = image.getWidth();double h = image.getHeight();Rect rect = new Rect();getDrawingRect(rect);int degrees = (int) (180 * rotation / Math.PI);canvas.rotate(degrees, rect.width() / 2, rect.height() / 2);canvas.drawBitmap(image, // (float) ((rect.width() - w) / 2),// (float) ((rect.height() - h) / 2),// paint);}public void setRotation(double rad) {rotation = rad;invalidate();}}總結
以上是生活随笔為你收集整理的获取Android设备的方向 ,使用加速度重力传感器的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Axure交互之部件行为
 - 下一篇: EZEMC测试软件_什么是EMC测试