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

前言

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

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

从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>
参考图:

监听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对象,现在,我们从这里面取到我们需要的高度值

先定义几个变量,我们需要拿的包含:
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值加上即可,就不贴代码了。

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/20076,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?