Android實(shí)現(xiàn)屏幕旋轉(zhuǎn)四個(gè)方向準(zhǔn)確監(jiān)聽
在做相機(jī)開發(fā)時(shí),遇到一個(gè)問題,就是需要監(jiān)聽屏幕旋轉(zhuǎn)。最簡單的就是使用onConfigurationChanged()和OrientationEventListener這兩種方法來實(shí)現(xiàn),但是最后都遇到了問題。
#1 一開始是使用onConfigurationChanged()這個(gè)回調(diào),重新Activity里面的這個(gè)方法就可以了,簡單又方便。用了之后發(fā)現(xiàn),它只能監(jiān)聽,橫屏切豎屏的情況。左橫屏切右橫屏是監(jiān)聽不到的,而且切完之后你也不知道是左橫屏還是右橫屏。下面是使用onConfigurationChanged()進(jìn)行監(jiān)聽的簡單使用。
@Override
? ? public void onConfigurationChanged(Configuration newConfig) {
? ? ? ? super.onConfigurationChanged(newConfig);
? ? ? ? if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
? ? ? ? ? ? // 橫屏
? ? ? ? }else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
? ? ? ? ? ? // 豎屏
? ? ? ? }
? ? }#2 之后又想到了OrientationEventListener來監(jiān)聽屏幕旋轉(zhuǎn)的實(shí)時(shí)角度,這個(gè)非常靈活,手機(jī)轉(zhuǎn)動實(shí)時(shí)角度都會回調(diào)出來。下面是使用OrientationEventListener的簡單實(shí)現(xiàn)。在適當(dāng)?shù)奈恢谜{(diào)用enable()和disable()來開啟和關(guān)閉監(jiān)聽。
class MyOrientationEventListener extends OrientationEventListener {
?
? ? ? ? private static final int SENSOR_ANGLE = 10;
?
? ? ? ? public MyOrientationEventListener(Context context) {
? ? ? ? ? ? super(context);
? ? ? ? }
?
? ? ? ? @Override
? ? ? ? public void onOrientationChanged(int orientation) {
? ? ? ? ? ? Log.d(TAG, "onOrientationChanged orientation=" + orientation);
? ? ? ? ? ? if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
? ? ? ? ? ? ? ? return; ?//手機(jī)平放時(shí),檢測不到有效的角度
? ? ? ? ? ? }
?
? ? ? ? ? ? //下面是手機(jī)旋轉(zhuǎn)準(zhǔn)確角度與四個(gè)方向角度(0 90 180 270)的轉(zhuǎn)換
? ? ? ? ? ? if (orientation > 360 - SENSOR_ANGLE || orientation < SENSOR_ANGLE) {
? ? ? ? ? ? ? ? orientation = 0;
? ? ? ? ? ? } else if (orientation > 90 - SENSOR_ANGLE && orientation < 90 + SENSOR_ANGLE) {
? ? ? ? ? ? ? ? orientation = 90;
? ? ? ? ? ? } else if (orientation > 180 - SENSOR_ANGLE && orientation < 180 + SENSOR_ANGLE) {
? ? ? ? ? ? ? ? orientation = 180;
? ? ? ? ? ? } else if (orientation > 270 - SENSOR_ANGLE && orientation < 270 + SENSOR_ANGLE) {
? ? ? ? ? ? ? ? orientation = 270;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? }
? ? }MyOrientationEventListener listener = new MyOrientationEventListener(this); ? ? ? ? listener.enable(); ? ? ? ? listener.disable();
但是,它只有當(dāng)手機(jī)豎直握持,然后左右轉(zhuǎn)動時(shí)是有效的,手機(jī)平放,左右轉(zhuǎn)動,是感應(yīng)不到角度變化的。原因是OrientationEventListener原理是只采集了Sensor X和Y方向上的加速度進(jìn)行計(jì)算的。可以從下面源碼中看到orientation的值只跟X和Y有關(guān)。(下面的源碼取自android.view.OrientationEventListener)而且使用這個(gè)判斷還有一個(gè)弊端,就是當(dāng)屏幕實(shí)際已經(jīng)進(jìn)行旋轉(zhuǎn)切換,但是OrientationEventListener回調(diào)的值還沒到達(dá)旋轉(zhuǎn)后的值。這就導(dǎo)致了系統(tǒng)屏幕旋轉(zhuǎn)了,但是我們app的UI因?yàn)闆]有收到callback而沒有改變的問題。
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;
? ? ? ??
? ? ? ? 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);
? ? ? ? ? ? }
? ? ? ? }#3 為了解決上述問題,其實(shí)最好的就是在系統(tǒng)屏幕旋轉(zhuǎn)的時(shí)候,能有個(gè)回調(diào),告訴我當(dāng)前是哪個(gè)角度,這樣就是最準(zhǔn)確的了。但是onConfigurationChanged只能告訴你是橫的還是豎的,雖然它做不了,但是給了一個(gè)方向。就是屏幕旋轉(zhuǎn)系統(tǒng)調(diào)用onConfigurationChanged的時(shí)候,肯定是知道旋轉(zhuǎn)后的角度的。根據(jù)閱讀源碼可知,當(dāng)屏幕旋轉(zhuǎn)時(shí),會調(diào)用IRotationWatcher#onRotationChanged(),但是對app來說是Hide的api,無法對他進(jìn)行監(jiān)聽。然后又發(fā)現(xiàn)android.hardware.LegacySensorManager類它在構(gòu)造函數(shù)里面,對IRotationWatcher進(jìn)行了注冊,onRotationChanged()返回的值,也會保存在sRotation,所以可以在這里做文章了。
public class ScreenOrientationListener extends OrientationEventListener {
?
? ? private static final String TAG = ScreenOrientationListener.class.getSimpleName();
? ? private int mOrientation;
? ? private OnOrientationChangedListener mOnOrientationChangedListener;
? ? private Context mContext;
? ? private Field mFieldRotation;
? ? private Object mOLegacy;
?
? ? public ScreenOrientationListener(Context context) {
? ? ? ? super(context);
? ? ? ? mContext = context;
? ? }
?
? ? public void setOnOrientationChangedListener(OnOrientationChangedListener listener) {
? ? ? ? this.mOnOrientationChangedListener = listener;
? ? }
?
? ? public int getOrientation() {
? ? ? ? int rotation = -1;
? ? ? ? try {
? ? ? ? ? ? if (null == mFieldRotation) {
? ? ? ? ? ? ? ? SensorManager sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
? ? ? ? ? ? ? ? Class clazzLegacy = Class.forName("android.hardware.LegacySensorManager");
? ? ? ? ? ? ? ? Constructor constructor = clazzLegacy.getConstructor(SensorManager.class);
? ? ? ? ? ? ? ? constructor.setAccessible(true);
? ? ? ? ? ? ? ? mOLegacy = constructor.newInstance(sensorManager);
? ? ? ? ? ? ? ? mFieldRotation = clazzLegacy.getDeclaredField("sRotation");
? ? ? ? ? ? ? ? mFieldRotation.setAccessible(true);
? ? ? ? ? ? }
? ? ? ? ? ? rotation = mFieldRotation.getInt(mOLegacy);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? Log.e(TAG, "getRotation e=" + e.getMessage());
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
// ? ? ? ?Log.d(TAG, "getRotation rotation=" + rotation);
?
? ? ? ? int orientation = -1;
? ? ? ? switch (rotation) {
? ? ? ? ? ? case Surface.ROTATION_0:
? ? ? ? ? ? ? ? orientation = 0;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case Surface.ROTATION_90:
? ? ? ? ? ? ? ? orientation = 90;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case Surface.ROTATION_180:
? ? ? ? ? ? ? ? orientation = 180;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case Surface.ROTATION_270:
? ? ? ? ? ? ? ? orientation = 270;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? break;
? ? ? ? }
// ? ? ? ?Log.d(TAG, "getRotation orientation=" + orientation);
? ? ? ? return orientation;
? ? }
?
? ? @Override
? ? public void onOrientationChanged(int orientation) {
? ? ? ? if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
? ? ? ? ? ? return; // 手機(jī)平放時(shí),檢測不到有效的角度
? ? ? ? }
? ? ? ? orientation = getOrientation();
? ? ? ? if (mOrientation != orientation) {
? ? ? ? ? ? mOrientation = orientation;
? ? ? ? ? ? if (null != mOnOrientationChangedListener) {
? ? ? ? ? ? ? ? mOnOrientationChangedListener.onOrientationChanged(mOrientation);
? ? ? ? ? ? ? ? Log.d(TAG, "ScreenOrientationListener onOrientationChanged orientation=" + mOrientation);
? ? ? ? ? ? }
? ? ? ? }
? ? }
?
? ? public interface OnOrientationChangedListener {
? ? ? ? void onOrientationChanged(int orientation);
? ? }
}上面的代碼,就是通過監(jiān)聽OrientationEventListener實(shí)時(shí)角度變化,然后使用反射的方法去獲取LegacySensorManager里面的rotation,這樣拿到的角度就是準(zhǔn)確的,在配合角度變化時(shí)才回調(diào)callback,就完美實(shí)現(xiàn)了4個(gè)方向角度旋轉(zhuǎn)時(shí)的監(jiān)聽。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用android studio導(dǎo)入模塊的兩種方法(超詳細(xì))
這篇文章主要介紹了使用android studio導(dǎo)入模塊的兩種方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09
安卓(android)怎么實(shí)現(xiàn)下拉刷新
這里我們將采取的方案是使用組合View的方式,先自定義一個(gè)布局繼承自LinearLayout,然后在這個(gè)布局中加入下拉頭和ListView這兩個(gè)子元素,并讓這兩個(gè)子元素縱向排列。對安卓(android)怎么實(shí)現(xiàn)下拉刷新的相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-04-04
Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的實(shí)現(xiàn)方案
這篇文章主要介紹了Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫,需要注意添加NativeNetInterceptor,如果有多個(gè)攔截器,例如LogInterceptors等等,需要將NativeNetInterceptor放到最后,需要的朋友可以參考下2022-05-05
Android實(shí)現(xiàn)ListView的A-Z字母排序和過濾搜索功能 實(shí)現(xiàn)漢字轉(zhuǎn)成拼音
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)ListView的A-Z字母排序和過濾搜索功能,實(shí)現(xiàn)漢字轉(zhuǎn)成拼音功能2017-06-06
Android BottomSheet實(shí)現(xiàn)可拉伸控件
這篇文章主要為大家詳細(xì)介紹了Android BottomSheet實(shí)現(xiàn)可拉伸控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
詳解Android10的分區(qū)存儲機(jī)制(Scoped Storage)適配教程
這篇文章主要介紹了詳解Android10的分區(qū)存儲機(jī)制(Scoped Storage)適配教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
詳解如何從原生Android 跳轉(zhuǎn)到hbuilder項(xiàng)目
這篇文章主要介紹了從原生Android 跳轉(zhuǎn)到hbuilder項(xiàng)目,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08

