作者:聲網(wǎng)Agora 工程師 黃龍飛
前言
在日常生活中使用手機(jī),通常都會(huì)遇到下面這兩種場景。
場景一:
在使用手機(jī)看視頻且設(shè)備開啟屏幕自動(dòng)旋轉(zhuǎn)時(shí),手機(jī)橫著拿和豎著拿,所看到的效果會(huì)不一樣。豎屏狀態(tài)下的展示如下圖(圖1)所示,橫屏狀態(tài)下的展示如圖2所示。
圖1.豎屏播放視頻
圖2.橫屏播放視頻
場景二:
在使用的手機(jī)應(yīng)用中,某些應(yīng)用的某些界面會(huì)根據(jù)當(dāng)前手機(jī)橫豎屏的狀態(tài),展示不同的界面效果,方便大家使用。比如AgoraVideoCall中的會(huì)議界面,具體如圖3 & 圖4所示:
圖3.AgoraVideoCall會(huì)議界面豎屏展示
圖4.AgoraVideoCall會(huì)議界面橫屏展示
上述中的兩種場景,都是根據(jù)用戶手持手機(jī)的方式及旋轉(zhuǎn)動(dòng)作自動(dòng)識(shí)別出用戶是想要橫屏展示還是豎屏展示的。關(guān)于“手機(jī)如何根據(jù)用戶的旋轉(zhuǎn)動(dòng)作而識(shí)別出用戶意圖"這個(gè)問題將會(huì)在下文中具體闡述。
加速度傳感器原理
手機(jī)實(shí)現(xiàn)這一功能的核心部件是加速度傳感器,在介紹加速度傳感器之前,先了解一下傳感器坐標(biāo)系。
- 傳感器坐標(biāo)系
通常,傳感器框架使用標(biāo)準(zhǔn)的 3 軸坐標(biāo)系來表示數(shù)據(jù)值。對于大多數(shù)傳感器,當(dāng)設(shè)備處于默認(rèn)屏幕方向時(shí),會(huì)相對于設(shè)備屏幕來定義坐標(biāo)系(參見圖 5)。當(dāng)設(shè)備處于默認(rèn)屏幕方向時(shí),X 軸為水平向右延伸,Y 軸為垂直向上延伸,Z 軸為垂直于屏幕向外延伸。在此坐標(biāo)系中,屏幕后面的坐標(biāo)將具有負(fù) Z 值。關(guān)于此坐標(biāo)系,特別需要注意的一點(diǎn)就是傳感器的坐標(biāo)系不會(huì)隨著設(shè)備的移動(dòng)而改變。
[圖片上傳失敗...(image-4c19c9-1610448627906)]
圖5.傳感器坐標(biāo)系(相對于設(shè)備)
加速度傳感器
加速度傳感器,它采用彈性敏感元件制成懸臂式位移器,與采用彈性敏感元件制成的儲(chǔ)能彈簧來驅(qū)動(dòng)電觸點(diǎn),完成從重力變化到電信號(hào)的轉(zhuǎn)換。例如:一個(gè)殼體與要測量加速度的物體,通過彈簧連接在一起,組成的一個(gè)重力感應(yīng)器,當(dāng)我們把殼體向上移動(dòng)時(shí),金屬球會(huì)因?yàn)閼T性向下拉伸彈簧,這時(shí)我們只需要測量出彈簧的拉伸量,我們就可以由此計(jì)算出重力。由此易得,X,Y,Z加速度計(jì),就能測量一個(gè)物體在三維空間中的運(yùn)動(dòng)方向。詳細(xì)說明請參閱[重力感應(yīng)原理]
加速度傳感器在Android橫豎屏切換中的應(yīng)用
加速度傳感器在移動(dòng)設(shè)備中的應(yīng)用眾多,本文主要介紹加速度傳感器在Android系統(tǒng)中橫豎屏切換的應(yīng)用,其原理主要為:通過監(jiān)聽加速度傳感器,實(shí)時(shí)獲得X、Y、Z三個(gè)方向的加速度值;當(dāng)4(XX + YY)>=Z*Z時(shí),開始計(jì)算設(shè)備在X與Y平面上的旋轉(zhuǎn)角度;最后根據(jù)旋轉(zhuǎn)角度計(jì)算出設(shè)備的橫豎屏狀態(tài)。下面貼出主要代碼。
/**
* 在onResume中,向SensorManager注冊監(jiān)聽加速度傳感器
*/
@Override
protected void onResume() {
super.onResume();
orientationListener = new OrientationListener(this);
orientationListener.enable();
}
public void enable() {
if (mSensor == null) {
Log.w(TAG, "Cannot detect sensors. Not enabled");
return;
}
if (mEnabled == false) {
if (localLOGV) Log.d(TAG, "OrientationEventListener enabled");
mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
mEnabled = true;
}
}
class SensorEventListenerImpl implements SensorEventListener {
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
/**
** 通過X、Y、Z三個(gè)方向的加速度值的變化,計(jì)算出設(shè)備旋轉(zhuǎn)的角度。
**/
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
int orientation = ORIENTATION_UNKNOWN;
float X = -values[_DATA_X];
float Y = -values[_DATA_Y];
float Z = -values[_DATA_Z];
float magnitude = X*X + Y*Y;
// Don't trust the angle if the magnitude is small compared to the y value
if (magnitude * 4 >= Z*Z) {
float OneEightyOverPi = 57.29577957855f;
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
orientation = 90 - (int)Math.round(angle);
// normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
if (mOldListener != null) {
mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values);
}
if (orientation != mOrientation) {
mOrientation = orientation;
onOrientationChanged(orientation);
}
}
}
public class OrientationListener extends OrientationEventListener {
private int degree = 0;//旋轉(zhuǎn)角度
private int mOrientation = 0;//2—橫屏 1-豎屏,0-未知
public OrientationListener(Context context) {
super(context);
}
/**
* 根據(jù)旋轉(zhuǎn)的角度,得出設(shè)備橫豎屏狀態(tài)
**/
@Override
public void onOrientationChanged(int orientation) {
degree = orientation;
if (degree > 0 && degree < 45) {
mOrientation = 1;
} else if (degree > 45 && degree < 135) {
mOrientation = 2;
} else if (degree > 135 && degree < 225) {
mOrientation = 1;
} else if (degree > 225 && degree < 315) {
mOrientation = 2;
} else if (degree > 315 && degree < 360) {
mOrientation = 1;
}
if (mOrientation == 2) {
Log.i("OrientationListener ", "橫屏");
} else if (mOrientation == 1) {
Log.i("OrientationListener ", "豎屏");
}
}
}
特別說明:
Math.atan2(-Y, X) 是計(jì)算從原點(diǎn)(0,0)到(x,y)點(diǎn)的線段與x軸正方向之間的平面角度(弧度值),
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi得到的是原點(diǎn)(0,0)到(x,y)點(diǎn)的線段與x軸正方向之間的角度,90 - (int)Math.round(angle)為設(shè)備旋轉(zhuǎn)的角度。
實(shí)戰(zhàn)演練
本節(jié)實(shí)現(xiàn)一個(gè)簡易的打高爾夫球游戲,練習(xí)一下加速度傳感器的運(yùn)用。該游戲?yàn)槎嗳擞螒颍螒蛞?guī)則為:參與者通過搖晃手機(jī)控制高爾夫球的移動(dòng),當(dāng)高爾夫球落到洞中則計(jì)1分,否則不計(jì)分,每人操作3次,得分最高者獲勝。游戲?qū)崿F(xiàn)原理是根據(jù)加速度傳感器獲得設(shè)備旋轉(zhuǎn)角度,實(shí)時(shí)計(jì)算并更新高爾夫球的位置,根據(jù)高爾夫球的位置與洞的重合度,判斷高爾夫球是否落入洞中。落入洞中則得分,否則,不計(jì)分。游戲具體實(shí)現(xiàn)見附件,演示如下。
鏈接: https://pan.baidu.com/s/1Kas3kL0fdaXbygGA--l7JQ 2 提取碼: 87nh
鏈接: https://pan.baidu.com/s/1yXNbbI3QhYG3BMZsyG2PSw 1 提取碼: 8uaz
擴(kuò)展
大多數(shù)移動(dòng)設(shè)備,除了上面介紹的加速度傳感器外,還有很多內(nèi)置傳感器,比如:重力傳感器、旋轉(zhuǎn)矢量傳感器、屏幕方向傳感器、溫度傳感器、光傳感器、壓力傳感器等(如需了解詳細(xì)信息,請參閱傳感器)。開發(fā)者可以根據(jù)這些傳感器,實(shí)現(xiàn)許多非常智能的功能。比如:
通過光線傳感器,自動(dòng)調(diào)節(jié)屏幕的亮度,保護(hù)用戶的眼睛
通過加速度傳感器,實(shí)現(xiàn)計(jì)步器和運(yùn)動(dòng)檢測功能
通過濕度傳感器和溫度傳感器,計(jì)算露點(diǎn)和絕對濕度
通過GPS,實(shí)現(xiàn)導(dǎo)航功能
參考文獻(xiàn):
https://developer.android.com/guide/topics/sensors/sensors_motion?hl=zh-cn 1
https://developer.android.com/guide/topics/sensors/sensors_overview?hl=zh-cn
https://github.com/googlearchive/android-AccelerometerPlay
https://v.qq.com/x/page/t050708r6ea.html
https://baike.baidu.com/item/重力感應(yīng)器?fromtitle=重力傳感器&fromid=3623984
附件
AccelerometerPlay.zip (5.0 KB)