文章目录
如何判断手机的横竖屏使用加速度计实现接口使用使用加速度计和磁力计获取手机倾斜角度加速度传感器在Android横竖屏切换中的应用如何判断手机的横竖屏
这个问题依赖与手机上的传感器
一般获取手机倾斜角度,用加速度计和磁力计去判断
如果只是判断横竖屏 可以只用加速度计
加速度计普及度高
使用加速度计实现
参考了AOSP的通用实现
public class AccelerometerListener {// Device orientationpublic static final int ORIENTATION_UNKNOWN = 0;public static final int ORIENTATION_VERTICAL = 1;public static final int ORIENTATION_HORIZONTAL = 2;private static final String TAG = "AccelerometerListener";private static final boolean DEBUG = true;private static final boolean VDEBUG = false;private static final int ORIENTATION_CHANGED = 1234;private static final int VERTICAL_DEBOUNCE = 100;private static final int HORIZONTAL_DEBOUNCE = 500;private static final double VERTICAL_ANGLE = 50.0;private SensorManager sensorManager;private Sensor sensor;// mOrientation is the orientation value most recently reported to the client.private int orientation;// mPendingOrientation is the latest orientation computed based on the sensor value.// This is sent to the client after a rebounce delay, at which point it is copied to// mOrientation.private int pendingOrientation;private OrientationListener listener;Handler handler =new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case ORIENTATION_CHANGED:synchronized (this) {orientation = pendingOrientation;if (DEBUG) {LogUtil.d(TAG,"orientation: "+ (orientation == ORIENTATION_HORIZONTAL? "horizontal": (orientation == ORIENTATION_VERTICAL ? "vertical" : "unknown")));}if (listener != null) {listener.orientationChanged(orientation);}}break;}}};SensorEventListener sensorListener =new SensorEventListener() {@Overridepublic void onSensorChanged(SensorEvent event) {onSensorEvent(event.values[0], event.values[1], event.values[2]);}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {// ignore}};public AccelerometerListener(Context context) {sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);}public void setListener(OrientationListener listener) {this.listener = listener;}public void enable(boolean enable) {if (DEBUG) {LogUtil.d(TAG, "enable(" + enable + ")");}synchronized (this) {if (enable) {orientation = ORIENTATION_UNKNOWN;pendingOrientation = ORIENTATION_UNKNOWN;sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);} else {sensorManager.unregisterListener(sensorListener);handler.removeMessages(ORIENTATION_CHANGED);}}}private void setOrientation(int orientation) {synchronized (this) {if (pendingOrientation == orientation) {// Pending orientation has not changed, so do nothing.return;}// Cancel any pending messages.// We will either start a new timer or cancel alltogether// if the orientation has not changed.handler.removeMessages(ORIENTATION_CHANGED);if (this.orientation != orientation) {// Set timer to send an event if the orientation has changed since its// previously reported value.pendingOrientation = orientation;final Message m = handler.obtainMessage(ORIENTATION_CHANGED);// set delay to our debounce timeoutint delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE : HORIZONTAL_DEBOUNCE);handler.sendMessageDelayed(m, delay);} else {// no message is pendingpendingOrientation = ORIENTATION_UNKNOWN;}}}private void onSensorEvent(double x, double y, double z) {if (VDEBUG) {LogUtil.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");}// If some values are exactly zero, then likely the sensor is not powered up yet.// ignore these events to avoid false horizontal positives.if (x == 0.0 || y == 0.0 || z == 0.0) {return;}// magnitude of the acceleration vector projected onto XY planefinal double xy = Math.hypot(x, y);// compute the vertical angledouble angle = Math.atan2(xy, z);// convert to degreesangle = angle * 180.0 / Math.PI;final int orientation =(angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);if (VDEBUG) {LogUtil.d(TAG, "angle: " + angle + " orientation: " + orientation);}setOrientation(orientation);}public interface OrientationListener {void orientationChanged(int orientation);}}
接口使用
public class ProximitySensorimplements AccelerometerListener.OrientationListener, InCallStateListener, AudioModeListener {private static final String TAG = ProximitySensor.class.getSimpleName();private final PowerManager powerManager;private final PowerManager.WakeLock proximityWakeLock;private final AudioModeProvider audioModeProvider;private final AccelerometerListener accelerometerListener;private final ProximityDisplayListener displayListener;private int orientation = AccelerometerListener.ORIENTATION_UNKNOWN;private boolean uiShowing = false;private boolean isPhoneOffhook = false;private boolean dialpadVisible;private boolean isAttemptingVideoCall;private boolean isVideoCall;private boolean isRttCall;public ProximitySensor(@NonNull Context context,@NonNull AudioModeProvider audioModeProvider,@NonNull AccelerometerListener accelerometerListener) {Trace.beginSection("ProximitySensor.Constructor");powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {proximityWakeLock =powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);} else {LogUtil.i("ProximitySensor.constructor", "Device does not support proximity wake lock.");proximityWakeLock = null;}this.accelerometerListener = accelerometerListener;this.accelerometerListener.setListener(this);displayListener =new ProximityDisplayListener((DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE));displayListener.register();this.audioModeProvider = audioModeProvider;this.audioModeProvider.addListener(this);Trace.endSection();}public void tearDown() {audioModeProvider.removeListener(this);accelerometerListener.enable(false);displayListener.unregister();turnOffProximitySensor(true);}/** Called to identify when the device is laid down flat. */@Overridepublic void orientationChanged(int orientation) {this.orientation = orientation;updateProximitySensorMode();}/** Called to keep track of the overall UI state. */@Overridepublic void onStateChange(InCallState oldState, InCallState newState, CallList callList) {// We ignore incoming state because we do not want to enable proximity// sensor during incoming call screen. We check hasLiveCall() because a disconnected call// can also put the in-call screen in the INCALL state.boolean hasOngoingCall = InCallState.INCALL == newState && callList.hasLiveCall();boolean isOffhook =InCallState.PENDING_OUTGOING == newState|| InCallState.OUTGOING == newState|| hasOngoingCall;DialerCall activeCall = callList.getActiveCall();boolean isVideoCall = activeCall != null && activeCall.isVideoCall();boolean isRttCall = activeCall != null && activeCall.isActiveRttCall();if (isOffhook != isPhoneOffhook|| this.isVideoCall != isVideoCall|| this.isRttCall != isRttCall) {isPhoneOffhook = isOffhook;this.isVideoCall = isVideoCall;this.isRttCall = isRttCall;orientation = AccelerometerListener.ORIENTATION_UNKNOWN;accelerometerListener.enable(isPhoneOffhook);updateProximitySensorMode();}}@Overridepublic void onAudioStateChanged(CallAudioState audioState) {updateProximitySensorMode();}public void onDialpadVisible(boolean visible) {dialpadVisible = visible;updateProximitySensorMode();}public void setIsAttemptingVideoCall(boolean isAttemptingVideoCall) {LogUtil.i("ProximitySensor.setIsAttemptingVideoCall","isAttemptingVideoCall: %b",isAttemptingVideoCall);this.isAttemptingVideoCall = isAttemptingVideoCall;updateProximitySensorMode();}/** Used to save when the UI goes in and out of the foreground. */public void onInCallShowing(boolean showing) {if (showing) {uiShowing = true;// We only consider the UI not showing for instances where another app took the foreground.// If we stopped showing because the screen is off, we still consider that showing.} else if (powerManager.isScreenOn()) {uiShowing = false;}updateProximitySensorMode();}void onDisplayStateChanged(boolean isDisplayOn) {LogUtil.i("ProximitySensor.onDisplayStateChanged", "isDisplayOn: %b", isDisplayOn);accelerometerListener.enable(isDisplayOn);}/*** TODO: There is no way to determine if a screen is off due to proximity or if it is legitimately* off, but if ever we can do that in the future, it would be useful here. Until then, this* function will simply return true of the screen is off. TODO: Investigate whether this can be* replaced with the ProximityDisplayListener.*/public boolean isScreenReallyOff() {return !powerManager.isScreenOn();}private void turnOnProximitySensor() {if (proximityWakeLock != null) {if (!proximityWakeLock.isHeld()) {LogUtil.i("ProximitySensor.turnOnProximitySensor", "acquiring wake lock");proximityWakeLock.acquire();} else {LogUtil.i("ProximitySensor.turnOnProximitySensor", "wake lock already acquired");}}}private void turnOffProximitySensor(boolean screenOnImmediately) {if (proximityWakeLock != null) {if (proximityWakeLock.isHeld()) {LogUtil.i("ProximitySensor.turnOffProximitySensor", "releasing wake lock");int flags = (screenOnImmediately ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);proximityWakeLock.release(flags);} else {LogUtil.i("ProximitySensor.turnOffProximitySensor", "wake lock already released");}}}/*** Updates the wake lock used to control proximity sensor behavior, based on the current state of* the phone.** <p>On devices that have a proximity sensor, to avoid false touches during a call, we hold a* PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock whenever the phone is off hook. (When held, that wake* lock causes the screen to turn off automatically when the sensor detects an object close to the* screen.)** <p>This method is a no-op for devices that don't have a proximity sensor.** <p>Proximity wake lock will be released if any of the following conditions are true: the audio* is routed through bluetooth, a wired headset, or the speaker; the user requested, received a* request for, or is in a video call; or the phone is horizontal while in a call.*/private synchronized void updateProximitySensorMode() {Trace.beginSection("ProximitySensor.updateProximitySensorMode");final int audioRoute = audioModeProvider.getAudioState().getRoute();boolean screenOnImmediately =(CallAudioState.ROUTE_WIRED_HEADSET == audioRoute|| CallAudioState.ROUTE_SPEAKER == audioRoute|| CallAudioState.ROUTE_BLUETOOTH == audioRoute|| isAttemptingVideoCall|| isVideoCall|| isRttCall);// We do not keep the screen off when the user is outside in-call screen and we are// horizontal, but we do not force it on when we become horizontal until the// proximity sensor goes negative.final boolean horizontal = (orientation == AccelerometerListener.ORIENTATION_HORIZONTAL);screenOnImmediately |= !uiShowing && horizontal;// We do not keep the screen off when dialpad is visible, we are horizontal, and// the in-call screen is being shown.// At that moment we're pretty sure users want to use it, instead of letting the// proximity sensor turn off the screen by their hands.screenOnImmediately |= dialpadVisible && horizontal;LogUtil.i("ProximitySensor.updateProximitySensorMode","screenOnImmediately: %b, dialPadVisible: %b, "+ "offHook: %b, horizontal: %b, uiShowing: %b, audioRoute: %s",screenOnImmediately,dialpadVisible,isPhoneOffhook,orientation == AccelerometerListener.ORIENTATION_HORIZONTAL,uiShowing,CallAudioState.audioRouteToString(audioRoute));if (isPhoneOffhook && !screenOnImmediately) {LogUtil.v("ProximitySensor.updateProximitySensorMode", "turning on proximity sensor");// Phone is in use! Arrange for the screen to turn off// automatically when the sensor detects a close object.turnOnProximitySensor();} else {LogUtil.v("ProximitySensor.updateProximitySensorMode", "turning off proximity sensor");// Phone is either idle, or ringing. We don't want any special proximity sensor// behavior in either case.turnOffProximitySensor(screenOnImmediately);}Trace.endSection();}/*** Implementation of a {@link DisplayListener} that maintains a binary state: Screen on vs screen* off. Used by the proximity sensor manager to decide whether or not it needs to listen to* accelerometer events.*/public class ProximityDisplayListener implements DisplayListener {private DisplayManager displayManager;private boolean isDisplayOn = true;ProximityDisplayListener(DisplayManager displayManager) {this.displayManager = displayManager;}void register() {displayManager.registerDisplayListener(this, null);}void unregister() {displayManager.unregisterDisplayListener(this);}@Overridepublic void onDisplayRemoved(int displayId) {}@Overridepublic void onDisplayChanged(int displayId) {if (displayId == Display.DEFAULT_DISPLAY) {final Display display = displayManager.getDisplay(displayId);final boolean isDisplayOn = display.getState() != Display.STATE_OFF;// For call purposes, we assume that as long as the screen is not truly off, it is// considered on, even if it is in an unknown or low power idle state.if (isDisplayOn != this.isDisplayOn) {this.isDisplayOn = isDisplayOn;onDisplayStateChanged(this.isDisplayOn);}}}@Overridepublic void onDisplayAdded(int displayId) {}}}
使用加速度计和磁力计获取手机倾斜角度
public class MainActivity extends Activity {private SensorManager mSensorManager;private Sensor accelerometer; // 加速度传感器private Sensor magnetic; // 地磁场传感器private TextView azimuthAngle;private float[] accelerometerValues = new float[3];private float[] magneticFieldValues = new float[3];private static final String TAG = "---MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 实例化传感器管理者mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);// 初始化加速度传感器accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 初始化地磁场传感器magnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);azimuthAngle = (TextView) findViewById(R.id.azimuth_angle_value);calculateOrientation();}@Overrideprotected void onResume() {// TODO Auto-generated method stub// 注册监听mSensorManager.registerListener(new MySensorEventListener(),accelerometer, Sensor.TYPE_ACCELEROMETER);mSensorManager.registerListener(new MySensorEventListener(), magnetic,Sensor.TYPE_MAGNETIC_FIELD);super.onResume();}@Overrideprotected void onPause() {// TODO Auto-generated method stub// 解除注册mSensorManager.unregisterListener(new MySensorEventListener());super.onPause();}// 计算方向private void calculateOrientation() {float[] values = new float[3];float[] R = new float[9];SensorManager.getRotationMatrix(R, null, accelerometerValues,magneticFieldValues);SensorManager.getOrientation(R, values);values[0] = (float) Math.toDegrees(values[0]);Log.i(TAG, values[0] + "");if (values[0] >= -5 && values[0] < 5) {azimuthAngle.setText("正北");} else if (values[0] >= 5 && values[0] < 85) {// Log.i(TAG, "东北");azimuthAngle.setText("东北");} else if (values[0] >= 85 && values[0] <= 95) {// Log.i(TAG, "正东");azimuthAngle.setText("正东");} else if (values[0] >= 95 && values[0] < 175) {// Log.i(TAG, "东南");azimuthAngle.setText("东南");} else if ((values[0] >= 175 && values[0] <= 180)|| (values[0]) >= -180 && values[0] < -175) {// Log.i(TAG, "正南");azimuthAngle.setText("正南");} else if (values[0] >= -175 && values[0] < -95) {// Log.i(TAG, "西南");azimuthAngle.setText("西南");} else if (values[0] >= -95 && values[0] < -85) {// Log.i(TAG, "正西");azimuthAngle.setText("正西");} else if (values[0] >= -85 && values[0] < -5) {// Log.i(TAG, "西北");azimuthAngle.setText("西北");}}class MySensorEventListener implements SensorEventListener {@Overridepublic void onSensorChanged(SensorEvent event) {// TODO Auto-generated method stubif (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {accelerometerValues = event.values;}if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {magneticFieldValues = event.values;}calculateOrientation();}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {// TODO Auto-generated method stub}}}
加速度传感器在Android横竖屏切换中的应用
通过监听加速度传感器,实时获得X、Y、Z三个方向的加速度值;当4(XX + YY)>=Z*Z时,开始计算设备在X与Y平面上的旋转角度;最后根据旋转角度计算出设备的横竖屏状态。/*** 在onResume中,向SensorManager注册监听加速度传感器*/@Overrideprotected 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;}}`````javaclass 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三个方向的加速度值的变化,计算出设备旋转的角度。**/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 valueif (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 rangewhile (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;//旋转角度private int mOrientation = 0;//2—横屏 1-竖屏,0-未知public OrientationListener(Context context) {super(context);}/*** 根据旋转的角度,得出设备横竖屏状态**/@Overridepublic 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) 是计算从原点(0,0)到(x,y)点的线段与x轴正方向之间的平面角度(弧度值),
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi得到的是原点(0,0)到(x,y)点的线段与x轴正方向之间的角度,90 - (int)Math.round(angle)为设备旋转的角度。