Android實現屏幕旋轉四個方向準確監聽
在做相機開發時,遇到一個問題,就是需要監聽屏幕旋轉。最簡單的就是使用onConfigurationChanged()和OrientationEventListener這兩種方法來實現,但是最後都遇到瞭問題。
#1 一開始是使用onConfigurationChanged()這個回調,重新Activity裡面的這個方法就可以瞭,簡單又方便。用瞭之後發現,它隻能監聽,橫屏切豎屏的情況。左橫屏切右橫屏是監聽不到的,而且切完之後你也不知道是左橫屏還是右橫屏。下面是使用onConfigurationChanged()進行監聽的簡單使用。
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){ // 橫屏 }else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ // 豎屏 } }
#2 之後又想到瞭OrientationEventListener來監聽屏幕旋轉的實時角度,這個非常靈活,手機轉動實時角度都會回調出來。下面是使用OrientationEventListener的簡單實現。在適當的位置調用enable()和disable()來開啟和關閉監聽。
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; //手機平放時,檢測不到有效的角度 } //下面是手機旋轉準確角度與四個方向角度(0 90 180 270)的轉換 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();
但是,它隻有當手機豎直握持,然後左右轉動時是有效的,手機平放,左右轉動,是感應不到角度變化的。原因是OrientationEventListener原理是隻采集瞭Sensor X和Y方向上的加速度進行計算的。可以從下面源碼中看到orientation的值隻跟X和Y有關。(下面的源碼取自android.view.OrientationEventListener)而且使用這個判斷還有一個弊端,就是當屏幕實際已經進行旋轉切換,但是OrientationEventListener回調的值還沒到達旋轉後的值。這就導致瞭系統屏幕旋轉瞭,但是我們app的UI因為沒有收到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 為瞭解決上述問題,其實最好的就是在系統屏幕旋轉的時候,能有個回調,告訴我當前是哪個角度,這樣就是最準確的瞭。但是onConfigurationChanged隻能告訴你是橫的還是豎的,雖然它做不瞭,但是給瞭一個方向。就是屏幕旋轉系統調用onConfigurationChanged的時候,肯定是知道旋轉後的角度的。根據閱讀源碼可知,當屏幕旋轉時,會調用IRotationWatcher#onRotationChanged(),但是對app來說是Hide的api,無法對他進行監聽。然後又發現android.hardware.LegacySensorManager類它在構造函數裡面,對IRotationWatcher進行瞭註冊,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; // 手機平放時,檢測不到有效的角度 } 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); } }
上面的代碼,就是通過監聽OrientationEventListener實時角度變化,然後使用反射的方法去獲取LegacySensorManager裡面的rotation,這樣拿到的角度就是準確的,在配合角度變化時才回調callback,就完美實現瞭4個方向角度旋轉時的監聽。
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Android如何監聽屏幕旋轉
- 如何在Android studio 中使用單例模式
- 輕松實現Android3D效果通俗易懂
- Android利用Sensor實現傳感器功能
- Android傳感器數據獲取的方法