当前位置: 首页 > news >正文

如何用正确的姿势监听Android屏幕旋转

作者:37手游移动客户端团队

背景

关于个人,前段时间由于业务太忙,所以一直没有来得及思考并且沉淀点东西;同时组内一个个都在业务上能有自己的思考和总结,在这样的氛围下,不由自主的驱使周末开始写点东西,希望自己除了日常忙于业务,可以沉淀点东西,加上自己的成长…

关于切入点,最近在做应⽤内悬浮球功能时,需要监听屏幕旋转事件来对悬浮球的位置进⾏调整,发现有些情况下并不能收到系统回调,思考了⼀翻,做了⼀个屏幕旋转的模拟监听,基本上能达到⽬的。

问题

悬浮球在停⽌拖拽后,需要贴边到⼿机屏幕的左右两侧。

在竖屏状态下,x坐标为0即为左边缘,x坐 标为屏幕宽度即为右边缘。

但是在横屏状态下,情况就⽐较复杂了。现在⼤部分Android⼿机都是刘 海屏的设计,在全屏状态下,悬浮球贴边时不能收到刘海下⾯去,不然就点不到了。

所以此时需要算 出刘海的宽度,以此宽度作为悬浮球左边的起始位置,这样悬浮球贴边的时候就不会躲到刘海下⾯ 去。 如下图所示

但是在屏幕旋转之后,刘海到了右边,左边就不应该以刘海的宽度作为悬浮球的起点了。 这样的话就需要监听屏幕的旋转了,配合屏幕⽅向的⻆度,就能正确判断。监听屏幕的旋转只需要重 写Activity的onConfiguratuonChanged⽣命周期。

override fun onConfigurationChanged(newConfig: Configuration) {super.onConfigurationChanged(newConfig)Log.i(TAG, "on configuration changed")
}

在AndroidManifest中配置

android:configChanges="orientation|screenSize"

此时发现了⼀个问题,当把Activity的screenOrientation设置成sensorLandscape时,即使屏幕旋转 也收不到这个回调(这个和之前的理解有点不⼀样)。于是将screenOrientation设置成sensor,屏 幕旋转就能正常回调到这⾥,多试⼏次发现,只有在横屏和竖屏之间切换时才能收到回调,如果直接 将横屏倒过来,就是横屏状态不变,⽅向调转,此时也不会收到回调。

解决思路

既然onConfigurationChanged收不到回调,还有另外⼀个办法,就是监听屏幕⽅向度数,代码如下

mOrientationEventListener = object : OrientationEventListener(this) {override fun onOrientationChanged(orientation: Int) {Log.i(TAG, "on orientation changed angle is $orientation")if (orientation > 340 || orientation < 20) {//0} else if (orientation in 71..109) {//90} else if (orientation in 161..199) {//180} else if (orientation in 251..289) {//270}}
}

通过度数来判断刘海是在左边还是在右边,即270度时在左边,90度时在右边。这种⽅式看起来可以 解决问题,但是多旋转⼏次就发现⼜有其他问题。按照正常思维,屏幕的显示⽅向应该和这个度数⼀ 致才对,即屏幕的显示应该是⾃上⽽下的。但是下图就不是这样。

此时度数为90,屏幕却倒⽴着显示的,并没有旋转成正⽴状态,但是按照上⾯的代码,会将90度判定 为正常90度正⽴显示的状态,此时去修改悬浮球的位置就是错误的。

那如果在收到onOrientationChanged这个回调时能判断⼀下屏幕显示的⽅向呢,就是在度数达到90 度范围时,同时判断屏幕的显示⽅向,即两个条件同时满⾜才判定成屏幕旋转了。

⽤下⾯的代码判定屏幕显示⽅向

val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as
WindowManager
val rotation = windowManager.defaultDisplay?.rotation
//rotation为常量0、1、2、3,分别表示屏幕的四个⽅向

通过这样的判断基本上能将屏幕旋转事件监听准确了,onOrientationChanged这个回调很灵敏,⼿ 机屏幕稍微动⼀下就会回调。那我希望模拟正常的屏幕旋转事件来修改悬浮球的位置,总不能很频繁 的刷新吧。这⾥做⼀下控制就好,全部代码如下:

object ScreenOrientationHelper {val ORIENTATION_TYPE_0 = 0val ORIENTATION_TYPE_90 = 90val ORIENTATION_TYPE_180 = 180val ORIENTATION_TYPE_270 = 270private var mOrientationEventListener: OrientationEventListener? = nullprivate var mScreenOrientationChangeListener:ScreenOrientationChangeListener? = nullprivate var currentType = ORIENTATION_TYPE_0fun init(context: Context, listener: ScreenOrientationChangeListener) {mScreenOrientationChangeListener = listenermOrientationEventListener = object :OrientationEventListener(context) {override fun onOrientationChanged(orientation: Int) {if (mScreenOrientationChangeListener == null) {return}if (orientation > 340 || orientation < 20) {//0if (currentType == 0) {return}if (getScreenRotation(context) == Surface.ROTATION_0) {mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_0)currentType = ORIENTATION_TYPE_0}} else if (orientation in 71..109) {//90if (currentType == 90) {return}val angle = getScreenRotation(context)if (angle == Surface.ROTATION_270) {mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_90)currentType = ORIENTATION_TYPE_90}} else if (orientation in 161..199) {//180if (currentType == 180) {return}val angle = getScreenRotation(context)if (angle == Surface.ROTATION_180) {mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_180)currentType = ORIENTATION_TYPE_180}} else if (orientation in 251..289) {//270if (currentType == 270) {return}val angle = getScreenRotation(context)if (angle == Surface.ROTATION_90) {mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_270)currentType = ORIENTATION_TYPE_270}}}}register()}private fun getScreenRotation(context: Context): Int {val windowManager =context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerreturn windowManager.defaultDisplay?.rotation ?: 0}fun register() {if (mOrientationEventListener != null) {mOrientationEventListener!!.enable()}}fun unRegister() {if (mOrientationEventListener != null) {mOrientationEventListener!!.disable()}}interface ScreenOrientationChangeListener {/**** @param orientation*/fun onChange(orientation: Int)}
}

使⽤的话,直接这样:

ScreenOrientationHelper.init(this, object :
ScreenOrientationHelper.ScreenOrientationChangeListener {override fun onChange(orientation: Int) {when(orientation) {ScreenOrientationHelper.ORIENTATION_TYPE_0 -> {}ScreenOrientationHelper.ORIENTATION_TYPE_90 -> {}ScreenOrientationHelper.ORIENTATION_TYPE_180 -> {}ScreenOrientationHelper.ORIENTATION_TYPE_270 -> {}}}
})

通过上⾯的代码发现,在onOrientationChanged回调90度范围内时,判定屏幕显示⽅向是和 Surface.ROTATION_270⽐较的,⽽270范围内时是和Surface.ROTATION_90⽐较的。看得出来⻆度 是顺时针递增的,⽽屏幕⽅向是逆时针计算度数的。

其他问题

在测试过程中,上⾯的⽅案还存在另外⼀个问题,虽然onOrientationChanged这个回调很灵敏,但 是也有度数不变⽽屏幕⽅向旋转的情况发⽣,即保持屏幕⽅向不变,⽽是增加屏幕的坡度(将⼿机⼀ 边贴在桌⾯,慢慢⽴起来),在坡度达到⼀定时,屏幕会发⽣旋转,此时onOrientationChanged是 不会回调的,因为没有变化。这样就收不到屏幕旋转的回调了,但是在实际⽤⼿机的场景中,这种情 况是⽐较少的,可以亲身试试看。

小结

在平时开发中,要区分是哪种状态横屏的场景⽐较少,否则我认为Android会给出准确的回调的。 Android设备碎⽚化严重,除了刘海,在屏幕的下边缘还有虚拟导航栏,在不同的系统设置下,这个 导航栏不显示状态会不⼀样。那么这时候在悬浮球贴边这个需求中就不仅仅要考虑刘海了,还得考虑 导航栏。更有甚者,在旋转过程中,虚拟导航栏会⼀直保持在⼀个⽅向,和刘海叠加。那么要清楚的 算位置,第⼀步就是要监听屏幕的旋转了。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

相关文章:

如何用正确的姿势监听Android屏幕旋转

作者&#xff1a;37手游移动客户端团队 背景 关于个人&#xff0c;前段时间由于业务太忙&#xff0c;所以一直没有来得及思考并且沉淀点东西&#xff1b;同时组内一个个都在业务上能有自己的思考和总结&#xff0c;在这样的氛围下&#xff0c;不由自主的驱使周末开始写点东西&…...

mysql高级三:sql性能优化+索引优化+慢查询日志

内容介绍 单表索引失效案例 0、思考题&#xff1a;如果把100万数据插入MYSQL &#xff0c;如何提高插入效率 &#xff08;1&#xff09;关闭自动提交&#xff0c;只手动提交一次 &#xff08;2&#xff09;删除除主键索引外其他索引 &#xff08;3&#xff09;拼写mysql可以执…...

HCIP VLAN--Hybrid接口

一、VLAN的特点 1、一个VLAN就是一个广播域&#xff0c;所以在同一个VLAN内部&#xff0c;计算机可以直接进行二层通信&#xff1b;而不同VLAN内的计算机&#xff0c;无法直接进行二层通信&#xff0c;只能进行三层通信来传递信息&#xff0c;即广播报文被限制在一个VLAN内。 …...

大数据开发面试必问:Hive调优技巧系列二

接上次分享的Hive调优技巧系列一&#xff1a; 数据倾斜、HiveJob优化 第1章 数据倾斜&#xff08;重点&#xff09; 绝大部分任务都很快完成&#xff0c;只有一个或者少数几个任务执行的很慢甚至最终执行失败&#xff0c;这样的现象为数据倾斜现象。 一定要和数据过量导致的…...

【C++】STL——list的模拟实现、构造函数、迭代器类的实现、运算符重载、增删查改

文章目录 1.模拟实现list1.1构造函数1.2迭代器类的实现1.3运算符重载1.4增删查改 1.模拟实现list list使用文章 1.1构造函数 析构函数 在定义了一个类模板list时。我们让该类模板包含了一个内部结构体_list_node&#xff0c;用于表示链表的节点。该结构体包含了指向前一个节点…...

vscode 插件::EIDE

最新最全 VSCODE 插件推荐&#xff08;2023版&#xff09;_vscode_白墨石-华为云开发者联盟 (csdn.net) 超好用的开发工具-VScode插件EIDE_vscode eide_桃成蹊2.0的博客-CSDN博客 Setup | Embedded IDE For VSCode (em-ide.com)...

Python 网络编程

Python 网络编程 Python 提供了两个级别访问的网络服务&#xff1a; 低级别的网络服务支持基本的 Socket&#xff0c;它提供了标准的 BSD Sockets API&#xff0c;可以访问底层操作系统 Socket 接口的全部方法。高级别的网络服务模块 SocketServer&#xff0c; 它提供了服务器…...

SQL 数据科学:了解和利用联接

推荐&#xff1a;使用 NSDT场景编辑器助你快速搭建可编辑的3D应用场景 什么是 SQL 中的连接&#xff1f; SQL 联接允许您基于公共列合并来自多个数据库表的数据。这样&#xff0c;您就可以将信息合并在一起&#xff0c;并在相关数据集之间创建有意义的连接。 SQL 中的连接类型…...

(统计学习方法|李航)第五章决策树——四五节:决策树的剪枝,CART算法

目录 一&#xff0c;决策数的剪枝 二&#xff0c;CART算法 1.CART生成 &#xff08;1&#xff09;回归树的生成 &#xff08;2&#xff09;分类树的生成 2.CART剪枝 &#xff08;1&#xff09;剪枝&#xff0c;形成一个子树序列 &#xff08;2&#xff09;在剪枝得到的子…...

C语言--结构体定义

整型数&#xff0c;浮点数&#xff0c;字符串是分散的数据表示&#xff0c;有时候我们需要很多类型表示一个整体&#xff0c;比如学生信息。 数组是元素类型一样的数据集合&#xff0c;如果是元素类型不同的数据集合&#xff0c;就要用到结构体 结构体一般是个模板&#xff0c;…...

解决Element Plus中Select在El Dialog里层级过低的问题(修改select选项框样式)

Element Plus是Vue.js的一套基于Element UI的组件库&#xff0c;提供了丰富的组件用于构建现代化的Web应用程序。其中&#xff0c;<el-select>是一个常用的下拉选择器组件&#xff0c;但在某些情况下&#xff0c;当<el-select>组件嵌套在<el-dialog>&#xf…...

【数据结构】二叉树 链式结构的相关问题

本篇文章来详细介绍一下二叉树链式结构经常使用的相关函数&#xff0c;以及相关的的OJ题。 目录 1.前置说明 2.二叉树的遍历 2.1 前序、中序以及后序遍历 2.2 层次遍历 3.节点个数相关函数实现 3.1 二叉树节点个数 3.2 二叉树叶子节点个数 3.3 二叉树第k层节点个数 3…...

【无标题】云原生在工业互联网的落地及好处!

什么是工业互联网&#xff1f; 工业互联网&#xff08;Industrial Internet&#xff09;是新一代信息通信技术与工业经济深度融合的新型基础设施、应用模式和工业生态&#xff0c;通过对人、机、物、系统等的全面连接&#xff0c;构建起覆盖全产业链、全价值链的全新制造和服务…...

人工智能在心电信号分类中的应用

目录 1 引言 2 传统机器学习中的特征提取与选择 3 深度学习中的特征提取与选择...

【Linux 网络】网络层协议之IP协议

IP协议 IP协议所处的位置网络层要解决的问题IP协议格式分片与组装网段划分特殊的IP地址IP地址的数量限制私网IP地址和公网IP地址路由 IP协议所处的位置 IP指网际互连协议&#xff0c;Internet Protocol的缩写&#xff0c;是TCP/IP体系中的网络层协议。 网络层要解决的问题 网络…...

.meta 文件

.meta 文件的作用简单来说是建立 Unity 与资源之间的“桥梁”。 在游戏中引用一个游戏资源&#xff0c;Unity 并不是直接按照文件的路径或者名称&#xff0c;而是使用一个独一无二的 GUID 来指向工程里该资源文件。 这个 GUID 就是存储在 Unity 工程为每一个资源和文件…...

CRITICAL_SECTION 用法

#include <stdio.h> #include <windows.h> typedef RTL_CRITICAL_SECTION CRITICAL_SECTION; CRITICAL_SECTION g_cs; //声明关键段 // 共享资源 char g_cArray[10]; unsigned int g_Count 0; DWORD WINAPI ThreadProc10(LPVOID pParam) { // 进入临界区 …...

汇川运动控制产品故障排查

针对汇川伺服产品&#xff08;IS600/IS620&#xff09;的基本检测和一些出现频率较高的故障进行检测判断方法&#xff0c;适用于服务人员在现场排查/判断机器故障时&#xff0c;准确定位问题。 一、简单故障排查 注1&#xff1a;接线错误&#xff1a;1、UVW相序是否正确&#…...

【Groups】50 Matplotlib Visualizations, Python实现,源码可复现

详情请参考博客: Top 50 matplotlib Visualizations 因编译更新问题&#xff0c;本文将稍作更改&#xff0c;以便能够顺利运行。 1 Dendrogram 树状图根据给定的距离度量将相似的点组合在一起&#xff0c;并根据点的相似性将它们组织成树状的链接。 新建文件Dendrogram.py: …...

windows安装kafka配置SASL-PLAIN安全认证

目录 1.Windows安装zookeeper&#xff1a; 1.1下载zookeeper 1.2 解压之后如图二 1.3创建日志文件 1.4复制 “zoo_sample.cfg” 文件 1.5更改 “zoo.cfg” 配置 1.6新建zk_server_jaas.conf 1.7修改zkEnv.cmd 1.8导入相关jar 1.9以上配置就配好啦&#xff0c;接下来启…...

【Linux】五种IO模型

文章目录 1. IO基本概念2. 五种IO模型2.1 五个钓鱼的例子2.2 五种IO模型2.2.1 阻塞IO2.2.2 非阻塞IO2.2.3 信号驱动IO2.2.4 IO多路转接2.2.5 异步IO 1. IO基本概念 认识IO IO就是输入和输出&#xff0c;在冯诺依曼体系结构中&#xff0c;将数据从输入设备拷贝到内存就叫输入&am…...

SCT82A30DHKR_5.5V-100V Vin同步降压控制器

SCT82A30是一款100V电压模式控制同步降压控制器&#xff0c;具有线路前馈。40ns受控高压侧MOSFET的最小导通时间支持高转换比&#xff0c;实现从48V输入到低压轨的直接降压转换&#xff0c;降低了系统复杂性和解决方案成本。如果需要&#xff0c;在低至6V的输入电压下降期间&am…...

备忘录模式(C++)

定义 在不破坏封装性的前提下&#xff0c;捕获一-个对象的内部状态&#xff0c;并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。 应用场景 ➢在软件构建过程中&#xff0c;某些对象的状态在转换过程中&#xff0c;可能由于某种需要&#xff0c;要…...

二叉排序树(二叉查找树)

二叉排序树&#xff08;二叉查找树&#xff09;的性质&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有结点的值均小于它的根结点的值。若它的右子树不为空&#xff0c;则右子树上所有结点的值均大于它的根将诶点的值。它的左、右子树也分别为二叉排序树。 对二叉…...

Python简单应用VII

题目 编程实现下述各题。 1.使用异常处理结构捕获多种可能的异常&#xff0c;如列表下标索引越界异常(IndexError)、试 图访问一个系统对象没有的属性所发生的异常(AttributeError)、读一个文件但该文件不存在。 2. 新建并打开文件stud1.txt&#xff0c;如果文件已存在就提示“…...

mysql--InnoDB存储引擎--架构和事务

MySQL进阶篇 文章目录 架构1、逻辑结构InnoDB 逻辑存储单元主层级关系图&#xff1a;1、表空间2、段3、区4、页5、行总结&#xff1a; 2、架构2、1 内存架构2、2 磁盘架构 3、事务3、1事务基础&#xff08;1&#xff09;事务&#xff08;2&#xff09;特性 架构 1、逻辑结构 I…...

0基础学习VR全景平台篇 第79篇:全景相机-泰科易如何直播推流

泰科易科技是中国的一家研发全景相机的高科技公司&#xff0c;前不久&#xff0c;在2020世界VR产业大会上发布了新一代5G VR直播影像采集终端--360starlight。以其出色的夜景成像效果和一“部”到位的直播方案重新定义了VR慢直播相机&#xff0c;对行业具有高度借鉴意义。 本文…...

代码调试4:实现退化模型的训练

代码调试:实现退化模型的训练 作者:安静到无声 个人主页 目录 代码调试:实现退化模型的训练问题1:如何在coco原始编码的基础上修改原始的文件?**方法1**:修改生成的文件**方法2**:直接修改源文件`instances_train2014.json`和`instances_val2014.json`问题2:构建退化后…...

8.7工作总结

一、我们想自定义一个titileBar出现如下这种情况&#xff0c;发现他原来的titileBar还未隐藏。 后来我尝试修改主题使得他没有主题noActionBar发现也不行&#xff0c;后来我参考原先我看过的项目使用了如下代码 this.getActionBar().hide();发现会报这个错误java.lang.NullPoi…...

数据库的约束 详解

一、约束的概述 1.概念:约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 2&#xff0e;目的:保证数据库中数据的正确、有效性和完整性。 3.分类: 约束描述关键字非空约束限制该字段的数据不能为nullNOT NULL唯一约束保证该字段的所有数据都是唯一、不…...