帶有g-sensor的android裝置上可通過api擷取到裝置的運動加速度,應用程式通過一些假設和運算,可以從加速度計算出裝置的方向
擷取裝置運動加速度的基本代碼是:
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
當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應用擷取螢幕旋轉角度的方法為:
根據上面的算法,我寫了一個“不倒翁”的demo,當裝置旋轉時,不倒翁始終是站立的。軟體市場上不少“水準尺”一類的應用,其實作原理應該是與此相同的
下載下傳demo源代碼
activity實作了sensoreventlistener,并且注冊到sensormanager。同時設定螢幕方向固定為landscape:
當g-sensor資料變化時的回調如下。這裡就是根據我們前面推論的算法計算出ui旋轉的角度,并且調用gsensitiveview.setrotation()方法通知view更新
gsensitiveview是擴充imageview的自定義類,主要是根據旋轉角度繪制圖檔: