自動(dòng)切換橫豎屏幕——手機(jī)加速度傳感器在Android橫豎屏切換中的應(yīng)用

作者:聲網(wǎng)Agora 工程師 黃龍飛

前言

在日常生活中使用手機(jī),通常都會(huì)遇到下面這兩種場景。
場景一:
在使用手機(jī)看視頻且設(shè)備開啟屏幕自動(dòng)旋轉(zhuǎn)時(shí),手機(jī)橫著拿和豎著拿,所看到的效果會(huì)不一樣。豎屏狀態(tài)下的展示如下圖(圖1)所示,橫屏狀態(tài)下的展示如圖2所示。

image

圖1.豎屏播放視頻

image

圖2.橫屏播放視頻

場景二:
在使用的手機(jī)應(yīng)用中,某些應(yīng)用的某些界面會(huì)根據(jù)當(dāng)前手機(jī)橫豎屏的狀態(tài),展示不同的界面效果,方便大家使用。比如AgoraVideoCall中的會(huì)議界面,具體如圖3 & 圖4所示:

image

圖3.AgoraVideoCall會(huì)議界面豎屏展示

image

圖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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容