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

前言

沉浸式的适配有多麻烦,相信大家既然来搜索这个,就说明都在为此苦恼,那么看看这篇文章吧,也许对你有所帮助(最下面有源码链接)

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

从adnroid 6.0开始,官方逐渐完善了这方面的api,直到android 11...

... 让我们直接开始吧

导入核心包

makefile 复制代码
老项目非androidx的请自行研究下,这里使用的是androidx,并且用的kotlin语言
本次实现方式跟windowInsets息息相关,这可真是个好东西
首先是需要导入核心包
androidx.core:core

kotlin可选择导入这个:
androidx.core:core-ktx
我用的版本是
androidx.core:core-ktx:1.12.0

开启 "沉浸式" 支持

javascript 复制代码
沉浸式原本的意思似乎是指全屏吧。。。算了,不管那么多,喊习惯了 沉浸式状态栏,就这么称呼吧。

在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

修改 状态栏、虚拟导航键 的图标颜色,可以在任意需要的时候设置,防止图标和字体颜色和背景色一致导致看不清

监听OnApplyWindowInsetsListener

kotlin 复制代码
//准备一个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对象,现在,我们从这里面取到我们需要的高度值

ini 复制代码
先定义几个变量,我们需要拿的包含:
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属性了

保留原本的Padding属性

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

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

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

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

如果有更好的优化方案,欢迎在github上提出,我们一起互相学习!

相关推荐
@OuYang7 小时前
android10 audio音量曲线
android
三爷麋了鹿11 小时前
VNC Viewer安卓版安装与操作
android
起个随便的昵称12 小时前
安卓入门十一 常用网络协议四
android·网络
BoomHe12 小时前
Android 车载性能优化-内存泄漏
android
起个随便的昵称13 小时前
安卓入门十三 常用功能模块一RxJava
android
工程师老罗13 小时前
Android笔试面试题AI答之Android基础(11)
android
叶羽西13 小时前
Android Camera压力测试工具
android
java_t_t13 小时前
安卓触摸事件的传递
android·java
千里马学框架15 小时前
千里马2024年终总结-android framework实战
android·framework·input·车机车载
tmacfrank16 小时前
Kotlin 协程基础知识总结五 —— 通道、多路复用、并发安全
android·开发语言·kotlin