全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2023-12-26_EdgeUtils:安卓沉浸式方案(edge to edge)封装

您的位置:首页 >> 新闻 >> 行业资讯

EdgeUtils:安卓沉浸式方案(edge to edge)封装 点击关注公众号回复”小册” 优惠券免费领!!EdgeUtils 项目地址:https://github.com/JailedBird/EdgeUtils 1、 接入方式EdgeUtils是基于androidx.core,对edge to edge沉浸式方案封装 ?? 接入方式: 添加jitpack仓库maven { url 'https://jitpack.io' } 添加依赖implementation 'com.github.JailedBird:EdgeUtils:1.0.0' 2、 使用方式2-1、 布局拓展全屏Activity中使用API edgeToEdge() 将开发者实现的布局拓展到整个屏幕, 同时为避免冲突, 将状态栏和到导航栏背景色设备为透明; 1669552233097-eacf0003-1ede-4035-a24e-ace16bfbe400.gif注意:edgeToEdge() 的参数withScrim表示是否启用系统默认的反差色保护, 不是很熟悉的情况下直接使用默认true即可; 2-2、 系统栏状态控制布局拓展之后, 开发者布局内容会显示在状态栏和导航栏区域, 造成布局和系统栏字体重叠(时间、电量……); 此时为确保系统栏字体可见,应该设置其字体;设置规则:白色(浅色)背景设置黑色字体(edgeSetSystemBarLight(true)),黑色(深色)背景设置白色字体(注:系统栏字体只有黑色和白色)(edgeSetSystemBarLight(false)); 如果未作夜间模式适配, 默认使用 edgeSetSystemBarLight(true)浅色模式即可! 综合1、2我们的基类可以写成如下的形式: abstract class BasePosActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { if (usingEdgeToEdgeTheme()) { defaultEdgeToEdge() } else { customThemeSetting() } super.onCreate(savedInstanceState) } } protected open fun defaultEdgeToEdge() { edgeToEdge(false) edgeSetSystemBarLight(true) } 2-3、 解决视觉冲突2-3-1、状态栏适配步骤一布局拓展全屏会导致视觉上的冲突, 下面是几种常见的思路:请灵活使用 布局中添加View(id="@+id/edge")使用heightToTopSystemWindowInsets API动态监听并修改View的高度为状态栏的高度 LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" View android:id="@+id/edge" android:layout_width="match_parent" android:layout_height="0dp" / xxx /LinearLayout binding.edge.heightToTopSystemWindowInsets() 直接获取状态栏的高度,API为:edgeStatusBarHeight;和1不同的是,1中View的height会随状态栏高度变化而变化,2不会;此外获取状态栏高度需要在View Attached之后才可以(否则高度为0),因此使用suspend函数等待Attached后才返回状态栏,确保在始终能获取到正确的状态栏高度! lifecycleScope.launch { val height = edgeStatusBarHeight() xxx } 针对有Toolbar的布局, 可直接为Toolbar加padding(or margin), 让padding的高度为状态栏高度!如果无效, 一般都与Toolbar的高度测量有关, 可以直接在Toolbar外层包上FrameLayout,为FrameLayout加padding, 详情阅读下文了解原理,从而灵活选择; fun View.paddingTopSystemWindowInsets() = applySystemWindowInsetsPadding(applyTop = true) fun View.marginTopSystemWindowInsets() = applySystemWindowInsetsMargin(applyTop = true) 2-3-2、 导航栏适配导航栏的适配原理和状态栏适配是非常相似的, 需要注意的是 导航栏存在三种模式: 全面屏模式虚拟导航栏虚拟导航条API已经针对导航栏高度、导航栏高度margin和padding适配做好了封装,使用者无需关心; fun View.paddingBottomSystemWindowInsets() = applySystemWindowInsetsPadding(applyBottom = true) fun View.marginBottomSystemWindowInsets() = applySystemWindowInsetsMargin(applyBottom = true) 适配思路是一致的,不再赘述; 2-4、 解决手势冲突手势冲突和视觉冲突产生的原理是相同的,不过是前者无形而后者有形;系统级别热区(如侧滑返回)优先级是要高于View的侧滑的, 因此有时候需要避开(情况很少) EdgeUtils主要工作只是做了视觉冲突的解决和一些API封装;使用者可以基于封装的API拓展,替换掉WindowInsetCompat.Type为你需要的类型; fun View.applySystemWindowInsetsPadding( applyLeft: Boolean = false, applyTop: Boolean = false, applyRight: Boolean = false, applyBottom: Boolean = false, ) { doOnApplyWindowInsets { view, insets, padding, _ - // val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) // 替换为Type.SYSTEM_GESTURES即可,其他类似 val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemGestures()) val left = if (applyLeft) systemBars.left else 0 val top = if (applyTop) systemBars.top else 0 val right = if (applyRight) systemBars.right else 0 val bottom = if (applyBottom) systemBars.bottom else 0 view.setPadding( padding.left + left, padding.top + top, padding.right + right, padding.bottom + bottom ) } } 3、 Edge教程3-1 何为edge to edge?如何彻底理解Edge to edge的思想呢? 或许你需要官方文章 , 也可以看的我写的翻译文章doc1?? 3-2 底层是如何实现的?了解Edge to edge原理后,你或许会好奇他是怎么实现的? 或许你需要Flywith24大佬的文章 , 也可看缩略文章doc2?? 3-3 其他杂项记录请看doc3 , 东西多但比较杂没整理?? 3-4 如何快速上手?EdgeUtils此框架基于androidx.core, 对WindowInsets等常见API进行封装,提供了稳定的API和细节处理;封装的API函数名称通俗易懂,理解起来很容易, 难点是需要结合 [Edge-to-edge](#Edge to edge) 的原理去进行灵活适配各种界面 项目中存在三个demo对于各种常见的场景进行了处理和演示 navigation-sample 基于Navigation的官方demo, 此demo展示了Navigation框架下这种单Activity多Fragment的沉浸式缺陷navigation-edge-sample 使用此框架优化navigation-sample, 使其达到沉浸式的效果immersion-sample 基于开源项目immersionbar中的demo进行EdgeUtils的替换处理, 完成大部分功能的替换 (注:已替换的会标记[展示OK],部分未实现)4、 注意事项4-1、 Toolbar通过paddingTop适配statusbar失效的问题很多时候, 状态栏的颜色和ToolBar的颜色是一致的, 这种情况下我们可以想到为ToolBar加 paddingTop = status_bar_height, 「但是注意如果你的Toolbar高度为固定、或者测量的时候没处理好padding,那么他就可能失真;」 「快速判断技巧:xml布局预览中(假设状态栏高度预估为40dp),使用tools:padding = 40dp, 通过预览查看这40dp的padding是否对预览变成预期之外的变形,如果OK那么直接使用paddingTopSystemWindowInsets为ToolBar大多是没问题的」 可以看下下面的2个例子: paddingTop = 0时候, 如下的代码:androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/teal_200" android:paddingTop="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:title="高度测试" / UI预览可以看到是这个样子的:image-20221124102655144paddingTop = 20时候, 如下的代码:androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/teal_200" android:paddingTop="20dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:title="高度测试" / 可以看到, Toolbar的总高度是不变的,内容高度下移20dp,这显然是不合理的;实际运行时动态为ToolBar添加statusbar的paddingTop肯定也会导致这样的问题image-20221124103232396解决方案: 1、 使用FrameLayout等常见ViewGroup包住ToolBar,将paddingTop高度设置到FrameLayout中, 将颜色teal_200设置到FrameLayout FrameLayout android:id="@+id/layout_tool" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="20dp" android:background="@color/teal_200" androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/teal_200" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:title="高度测试" / /FrameLayout 如下: image-202211241035426512、 在ToolBar外层直接封装FrameLayout(LinearLayout等也可, 下文统一用FrameLayout替代); 我相信大家一般都不会直接使用原生的Toolbar, 每个公司或多或少的都封装了一些自定义ToolBar;按照上述1的思路, 我们不难发现: 如果自定义ToolBar继承自FrameLayout(或者说Toolbar最外层被FrameLayout包住), 直接将paddingTop加到自定义ToolBar即可;当然有些做的好的公司可能会直接通过继承ViewGroup(如原生ToolBar), 这个时候可能就只能用方案1了;当然上述几点都是具体问题具体分析, 大家可以在预览界面临时加paddingTop,看看实际是什么样的, 便于大家尽早发现问题;可以参考下BottomNavigationView的源码, 它间接继承自FrameLayout, 内部对paddingBottom自动适配了navigation_bar_height; 这个思路和ImmersionBar的 状态栏与布局顶部重叠解决方案 类似,不同的是,ImmersionBar使用的是固定的高度,而方案1是动态监听状态栏的高度并设置FrameLayout的paddingTop; 「注:上述的paddingTop = 20dp, 只是方便预览添加的, 运行时请通过API动态设置paddingTop = statusBar」 「3、 添加空白View,通过代码设置View高度为导航栏、状态栏高度时,存在坑;约束布局中0dp有特殊含义,可能导致UI变形,需要注意哈!特别是处理导航栏的时候,全屏时导航栏高度为0,就会导致View高度为0,如果有组件依赖他,可能会出现奇怪问题,因此最好现在布局预览中排查下」 4-2、 Bug&兼容性(框架已修复)直接使用Edge to edge(参照google官方文档)存在一个大坑:调用hide隐藏状态栏后会导致状态栏变黑, 并且内容区域无法铺满 详细描述看这里:point_right: WindowInsetsControllerCompat.hide makes status bar background undrawable private fun setWindowEdgeToEdge(window: Window) { WindowCompat.setDecorFitsSystemWindows(window, false) window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT } WindowCompat.getInsetsController(this, this.decorView)?.let { it.systemBarsBehavior = behavior it.hide(WindowInsetsCompat.Type.statusBars()) } 具体表现下图这个样子: image-20221125143449641解决方案如下 :point_down: How to remove top status bar black background object EdgeUtils { /** To fix hide status bar black background please using this post * youtube: https://www.youtube.com/watch?v=yukwno2GBoI * stackoverflow: https://stackoverflow.com/a/72773422/15859474 * */ private fun Activity.edgeToEdge() { requestWindowFeature(Window.FEATURE_NO_TITLE) if (Build.VERSION.SDK_INT = Build.VERSION_CODES.P) { window.attributes.layoutInDisplayCutoutMode = WindowManager .LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } setWindowEdgeToEdge(this.window) } private fun setWindowEdgeToEdge(window: Window) { WindowCompat.setDecorFitsSystemWindows(window, false) window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT } 4-3、 如何去掉scrim?在导航栏设置为全透明时, 部分机型就会出现scrim半透明遮罩,考虑到样式有点丑陋, 直接将其修改为#01000000, 这样看起来也是完全透明的, 但是系统判定其alpha不为0, 不会主动添加scrim的;【具体请看官方文档】 private fun setWindowEdgeToEdge(window: Window) { WindowCompat.setDecorFitsSystemWindows(window, false) /** using not transparent avoid scrim*/ Color.parseColor("#01000000").let { color - window.statusBarColor = color window.navigationBarColor = color } } 4-4 、 禁止View的多次监听一个View只能绑定一次ApplyWindowInset的监听,多次绑定可能会导致之前的失效或者出现奇怪问题!!! 5、 参考资料Android Detail:Window 篇-WindowInsets & fitsSystemWindow官方文档,必看!Lay out your app within window insets点击关注公众号回复”小册” 优惠券免费领!! 阅读原文

上一篇:2021-03-06_力压清华北大!深圳大学,世界第三!国际专利申请最新榜单出炉 下一篇:2020-08-19_智慧城市:从“技术承诺”到“权利接口”

TAG标签:

14
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价