Android实现屏幕旋转四个方向准确监听
作者:小风666 发布时间:2022-06-07 08:57:32
在做相机开发时,遇到一个问题,就是需要监听屏幕旋转。最简单的就是使用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个方向角度旋转时的监听。
来源:https://blog.csdn.net/u011803341/article/details/96431142


猜你喜欢
- hibernate一级缓存和二级缓存的区别缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用
- 本文给大家带来一个很实用的小控件ClearEditText,就是在Android系统的输入框右边加入一个小图标,点击小图标可以清除输入框里面
- 概述本文的编写初衷,是想了解一下Spring Boot2中,具体是怎么序列化和反序列化JSR 310日期时间体系的,Spring MVC应用
- 前言数独是一种有趣的智力游戏,但是部分高难度数独在求解过程中经常出现大量单元格有多个候选数字可以填入,不得不尝试填写某个数字然后继续推导的方
- 本文实例为大家分享了Java实现五子棋网络版的具体代码,供大家参考,具体内容如下需求分析:对于网络五子棋而言,在普通五子棋的基础上需要添加以
- 之前文章介绍过了Fluent基本框架等,其中有几个重要的方法用到了IQuery和IUpdate对象。 这2个对象是FluentMybatis
- 在开发的过程中,往往会需要在组件中添加一些按钮,用于执行一些自定义的操作。例如你有一个组件A,里面有一个List<Collider&g
- 本文为大家分享了Android使用线程获取网络图片的具体代码,供大家参考,具体内容如下AndroidManifest.xml &n
- Android实现界面内嵌多种卡片视图,具体内容如下效果如图所示:1.选择某个界面时,对应的第几个小圆点亮:通过selector制造圆点和进
- 一、关于Spring Cache缓存在现在的应用中越来越重要,Spring从3.1开始定义了org.springframework.cach
- 概述非对称加密算法与对称加密算法的主要差别在于非对称加密算法用于加密和解密的密钥不相同,非对称加密算法密钥分为公钥和私钥,公钥加密只能用私钥
- 使用Spring data JPA开发已经有一段时间了,这期间学习了一些东西,也遇到了一些问题,在这里和大家分享一下。前言:Spring d
- 简介我们在前面的Android教程中已经提到过这么一件事:Android在启动后会有一个主线程。它不允许任何子线程去改变主UI线程里的内容。
- 前言 因为自己在做的一个小软件里面需要用到从A-Z排序的ListView,所以自然而然的想到了微信的联系人,我想要的就是那样的效果。本来没
- 1. Mybatis分页插件1.1 分页插件介绍分页可以将很多条结果进行分页显示。如果当前在第一页,则没有上一页。如果当前在最后一页,则没有
- 想要实现一个功能:同一个用户在两个不同的浏览器中登录,后面的踢掉之前的登录。本来的思路是在httpSession * 中进行判断。但是在使用
- 项目结构把源码 clone 下来 , 可以看到 retrofit 整体结构如下图 http包目录下就是一些http协议常用接口 , 比如 请
- 本文实例为大家分享了C++实现扫雷游戏的具体代码,供大家参考,具体内容如下#include<stdio.h>#include&l
- Servlet3.0的出现是servlet史上最大的变革,其中的许多新特性大大的简化了web应用的开发,为广大劳苦的程序员减轻了压力,提高了
- 引言异步蓝图节点:在蓝图节点的右上角有时钟图标。注意:异步节点可以在EventGraph/Macros中使用,但是无法在蓝图函数中使用。AI