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)動(dòng)實(shí)時(shí)角度都會(huì)回調(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)動(dòng)時(shí)是有效的,手機(jī)平放,左右轉(zhuǎn)動(dòng),是感應(yīng)不到角度變化的。原因是OrientationEventListener原理是只采集了Sensor X和Y方向上的加速度進(jìn)行計(jì)算的??梢詮南旅嬖创a中看到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í),會(huì)調(diào)用IRotationWatcher#onRotationChanged(),但是對(duì)app來說是Hide的api,無法對(duì)他進(jìn)行監(jiān)聽。然后又發(fā)現(xiàn)android.hardware.LegacySensorManager類它在構(gòu)造函數(shù)里面,對(duì)IRotationWatcher進(jìn)行了注冊,onRotationChanged()返回的值,也會(huì)保存在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)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android應(yīng)用中使用ViewPager和ViewPager指示器來制作Tab標(biāo)簽
這篇文章主要介紹了Android中使用ViewPager和ViewPager指示器來制作Tab標(biāo)簽的方法,ViewPager指示器ViewPageIndicator是一個(gè)開源庫,文中舉了一個(gè)仿網(wǎng)易新聞客戶端Tab標(biāo)簽的例子,需要的朋友可以參考下2016-03-03Android連接MySQL數(shù)據(jù)庫并進(jìn)行增刪改查操作示例講解
這篇文章主要介紹了Android 連接MySQL數(shù)據(jù)庫并進(jìn)行增刪改查操作示例講解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Android使用ViewFlipper實(shí)現(xiàn)圖片切換功能
這篇文章主要為大家詳細(xì)介紹了Android使用ViewFlipper實(shí)現(xiàn)圖片切換功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android仿硅谷商城實(shí)現(xiàn)購物車實(shí)例代碼
這篇文章主要介紹了Android購物車編輯實(shí)現(xiàn),小編覺得挺不錯(cuò)的,一起跟隨小編過來看看吧2018-05-05Android判斷某個(gè)權(quán)限是否開啟的方法
今天小編就為大家分享一篇Android判斷某個(gè)權(quán)限是否開啟的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07android獲取附近藍(lán)牙設(shè)備并計(jì)算距離的實(shí)例代碼
下面小編就為大家分享一篇android獲取附近藍(lán)牙設(shè)備并計(jì)算距離的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01android使用Ultra-PullToRefresh實(shí)現(xiàn)下拉刷新自定義代碼
本篇文章主要介紹了android使用Ultra-PullToRefresh實(shí)現(xiàn)下拉刷新新自定義,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02android activity設(shè)置無標(biāo)題實(shí)現(xiàn)全屏
本文將詳細(xì)介紹Android如何設(shè)置Activity全屏和無標(biāo)題的實(shí)現(xiàn)方法,需要的朋友可以參考下2012-12-12