前语

沉溺式的适配有多费事,信任我们已然来搜索这个,就说明都在为此苦恼,那么看看这篇文章吧,或许对你有所协助(最下面有源码链接)

有写的不对的当地,欢迎指出

从adnroid 6.0初步,官方逐步完善了这方面的api,直到android 11…

… 让我们直接初步吧

导入核心包

老项目非androidx的请自行研究下,这儿运用的是androidx,并且用的kotlin言语
本次完成方式跟windowInsets休戚相关,这可真是个好东西
首先是需求导入核心包
androidx.core:core
kotlin可选择导入这个:
androidx.core:core-ktx
我用的版本是
androidx.core:core-ktx:1.12.0

敞开 “沉溺式” 支撑

沉溺式原本的意思似乎是指全屏吧。。。算了,不管那么多,喊习惯了 沉溺式状态栏,就这么称号吧。
在activity 的oncreate里调用
//将decorView的fitSystemWindows特色设置为false
WindowCompat.setDecorFitsSystemWindows(window, false)
//设置状态栏颜色为通明
window.statusBarColor = Color.TRANSPARENT
//是否需求改动状态栏上的 图标、字体 的颜色
//获取InsetsController
val insetsController = WindowCompat.getInsetsController(window, window.decorView)
//mask:遮罩 默许是false 
//mask = true 状态栏字体颜色为黑色,一般在状态栏下面的布风光为浅色时运用
//mask = false 状态栏字体颜色为白色,一般在状态栏下面的布风光为深色时运用
var mask = true
insetsController.isAppearanceLightStatusBars = mask
//底部导航栏是否需求批改
//android Q+ 去掉虚拟导航键 的灰色半通明遮罩
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    window.isNavigationBarContrastEnforced = false
}
//设置虚拟导航键的 布风光为通明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    //8.0+ 虚拟导航键图标颜色可以批改,所以布景可以用通明
    window.navigationBarColor = Color.TRANSPARENT
} else {
    //低版本因为导航键图标颜色无法批改,建议用黑色,不要通明
    window.navigationBarColor = Color.BLACK
}
//是否需求批改导航键的颜色,mask 同上面状态栏的相同
insetsController.isAppearanceLightNavigationBars = mask
批改 状态栏、虚拟导航键 的图标颜色,可以在任意需求的时分设置,防止图标和字体颜色和布风光共同导致看不清
补偿一下:
状态栏和虚拟导航栏的布风光要注意以下问题:
1.在低于6.0的手机上,状态栏上的图标、字体颜色是白色且不支撑批改的,MIUI,Flyme这些在外,因为它们有自己的api能完成批改颜色
2.在低于8.0的手机上,虚拟导航栏的图标、字体颜色是白色且不支撑批改的,MIUI,Flyme这些在外,因为他们有自己的api能完成批改颜色
解决计划:
低于指定版本的系统上,对应的颜色就不要用通明,除非你的APP页面是深色布景,不然,建议采用半通明的灰色
在带有刘海或许挖孔屏上,横屏时刘海或许挖孔的那条边会有黑边,解决方法是:
给APP的主题v27加上
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
参考图:

Android 沉溺式状态栏,通明状态栏 采用系统api,超简单近乎完美的实现

监听OnApplyWindowInsetsListener

//准备一个boolean变量 作为是否在跑动画的符号
var flagProgress = false
//这儿可以运用decorView或许是任意view
val view = window.decorView
//监听windowInsets改变
ViewCompat.setOnApplyWindowInsetsListener(view) { view: View, insetsCompat: WindowInsetsCompat ->
    //假设要合作下面的setWindowInsetsAnimationCallback一重用的话,一定要记得,onProgress的时分,这儿做个拦截,直接回来 insets
    if (flagProgress) return@setOnApplyWindowInsetsListener insetsCompat
    //在这儿初步给需求的控件分发windowInsets
    //终究,选择不消费这个insets,也可以选择消费掉,不在往子控件分发
    insetsCompat
}
//带滑润过渡的windowInsets改变,ViewCompat中的这个,官方提供了 api 21-api 29的支撑,原本这个只支撑 api 30+的,适当不错!
//启用setWindowInsetsAnimationCallback的一起,也必需求启用上面的setOnApplyWindowInsetsListener,不然在某些情况下,windowInsets改动了,可是因为不会触发setWindowInsetsAnimationCallback导致padding没有更新到UI上
//DISPATCH_MODE_CONTINUE_ON_SUBTREE这个代表动画事件持续分发下去给子View
ViewCompat.setWindowInsetsAnimationCallback(view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
    override fun onProgress(insetsCompat: WindowInsetsCompat, runningAnimations: List<WindowInsetsAnimationCompat>): WindowInsetsCompat {
        //每一帧的windowInsets
        //可以在这儿分发给需求的View。例如给一个谈天窗口包含editText的布局设置这个padding,可以完成键盘弹起时,在底部的editText跟着键盘一起滑上去,参考微信谈天界面,这个比微信还丝滑(android 11+最完美)。
        //终究,直接原样return,不消费
        return insetsCompat
    }
    override fun onEnd(animation: WindowInsetsAnimationCompat) {
        super.onEnd(animation)
        //动画完毕,将符号置否
        flagProgress = false
    }
    override fun onPrepare(animation: WindowInsetsAnimationCompat) {
        super.onPrepare(animation)
        //动画准备初步,在这儿可以记载一些UI状态信息,这儿将符号设置为true
        flagProgress = true
    }
})

读取高度值

经过上面的监听,我们能拿到WindowInsetsCompat目标,现在,我们从这儿面取到我们需求的高度值

先界说几个变量,我们需求拿的包含:
1. 刘海,挖空区域所占有的宽度或许是高度
2. 被系统栏遮挡的区域
3. 被输入法遮挡的区域
//cutoutPadding 刘海,挖孔区域的padding
var cutoutPaddingLeft = 0
var cutoutPaddingTop = 0
var cutoutPaddingRight = 0
var cutoutPaddingBottom = 0
//获取刘海,挖孔的高度,因为这个不是一切手机都有,所以,需求判空
insetsCompat.displayCutout?.let { displayCutout ->
    cutoutPaddingTop = displayCutout.safeInsetTop
    cutoutPaddingLeft = displayCutout.safeInsetLeft
    cutoutPaddingRight = displayCutout.safeInsetRight
    cutoutPaddingBottom = displayCutout.safeInsetBottom
}
//systemBarPadding 系统栏区域的padding
var systemBarPaddingLeft = 0
var systemBarPaddingTop = 0
var systemBarPaddingRight = 0
var systemBarPaddingBottom = 0
//获取系统栏区域的padding
//系统栏 + 输入法
val systemBars = insetsCompat.getInsets(WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars())
//左右两侧的padding一般直接赋值即可,假设横屏状态下,虚拟导航栏在侧边,那么systemBars.left或许systemBars.right的值便是它的宽度,竖屏情况下,一般都是0
systemWindowInsetLeft = systemBars.left
systemWindowInsetRight = systemBars.right
//这儿判别下输入法 和 虚拟导航栏是否存在,假设存在才设置paddingBottom
if (insetsCompat.isVisible(WindowInsetsCompat.Type.ime()) || insetsCompat.isVisible(WindowInsetsCompat.Type.navigationBars())) {
    systemWindowInsetBottom = systemBars.bottom
}
//相同判别下状态栏
if (insetsCompat.isVisible(WindowInsetsCompat.Type.statusBars())) {
    systemWindowInsetTop = systemBars.top
}
到这儿,我们需求的信息现已全部获取到了,接下来便是依据需求,设置padding特色了
补偿一下:
我发现在低于android 11的手机上,insets.isVisible(Type)回来一向为true
并且,即使系统栏被隐藏,systemBars.top, systemBars.bottom也一向会有高度
所以这儿

保存原本的Padding特色

上述获取的值,直接去设置padding的话,会导致原本的padding特色失效,所以我们需求在首次设置监听,先保存一份原本的padding特色,在终究设置padding的时分,把这份原本的padding值加上即可,就不贴代码了。

第一次写文章,写的粗糙了点

可能我写的不太好,没看懂也没关系,直接去看完好代码吧

我专门写了个小工具,可以去看看: 沉溺式系统栏 小工具

假设有更好的优化计划,欢迎在github上提出,我们一起互相学习!