Android实现屏幕旋转四个方向准确监听

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个方向角度旋转时的监听。

推荐阅读