【大道至简】官方兼容到android13+的获取系统屏幕高度, statusbar,navBar

android在屏幕高度和app高度,statusbar, navigationbar的高度处理上,迭代了好多版本。
android11, android12都有新的api和过时的api标记。

涉及的api类似如下:
windowManager,defaultDisplay, Context.display, DecorView, windowInsets, Compat兼容库, getRealSize, getSize,
currentWindowMetrics, maximumWindowMetrics, 通过resources获取资源id navigation_bar_height等等等等。

网上的帖子跟api一样,实现的五花八门太多了。

花了2天时间,终于找到了官方,而且最合适的方案:
全网似乎都没有这么简洁和准确的方案了:
implemention “androidx.window:window:1.2.0” 引入后,写上下面2个函数:

//随意调用,官方出品最精简,最准确。
fun Activity.getScreenFullSize() : Pair<Int, Int> {
    val m = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
    //computeMaximumWindowMetrics(this) 区别就是多屏,类似华为推上去的效果。不分屏就是一样的。
    return m.bounds.width() to m.bounds.height()
}

/**
* 获取当前的statusBar的高度和navigationBar高度。如果在onCreate,onStart, onResume调用,必须经过一轮View.post之后。
* 如果你明确知道activity已经ok则直接调用。
*/
fun Activity.currentStatusBarAndNavBarHeight() : Pair<Int, Int>? {
    val insets = ViewCompat.getRootWindowInsets(window.decorView) ?: return null
    val nav = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
    val sta = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
    return sta to nav
}

接下来介绍一下:

1. 想要屏幕高宽

getScreenFullSize()
即:WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)

无需等待界面渲染成功,即在onCreate就可以调用,而且里面已经做了低版本兼容,感谢jetpack window库。
获取的就是整个屏幕的高度。
包含了statusBar,navigationBar的高度一起。与adb shell wm size一致。
这个方法100%可靠。虽然我们看api上描述说低版本(由于navigationBar可能获取不到)近似值,但也是最接近最合理的值,不会是0的。所以这个函数就是目前集大成者。
点击computeCurrentWindowMetrics进入查看源码:

val bounds = if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
    currentWindowBounds(activity) //往下就是wm.currentWindowMetrics.bounds
} else if (Build.VERSION.SDK_INT >= VERSION_CODES.Q) {
    computeWindowBoundsQ(activity) //反射Configuration windowConfiguration bounds
} else if (Build.VERSION.SDK_INT >= VERSION_CODES.P) {
    computeWindowBoundsP(activity) //反射Configuration windowConfiguration bounds
} else if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
    computeWindowBoundsN(activity) //display realSize + navigationHeight Id获取
} else {
    computeWindowBoundsIceCreamSandwich(activity)
}

大家放心使用getScreenFullSize,WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this),来获取屏幕的高宽。官方出品最精简,最准确。

2. 想要SystembarHeight和navBarHeight

大家一般都见过这个代码:

fun Window.transparentStatusBar(
    isBlackStatusBarTextColor: Boolean? = null,
    isBlackNavigationBarTextColor: Boolean? = null,
    crossinline insetsBlock: (
        insets: WindowInsetsCompat,
        statusBarsHeight: Int,
        navigationBarHeight: Int
    ) -> WindowInsetsCompat = {insets, _, _ -> insets}
) {
    ViewCompat.setOnApplyWindowInsetsListener(decorView) { _, insets ->
        val bottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
        val top = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
        insetsBlock.invoke(
            insets,
            top,
            bottom
        )
    }
    WindowCompat.setDecorFitsSystemWindows(this, false)
    statusBarColor = Color.TRANSPARENT

    if (isBlackStatusBarTextColor != null || isBlackNavigationBarTextColor != null) {
        WindowCompat.getInsetsController(this, decorView).apply {
            if (isBlackStatusBarTextColor != null) {
                isAppearanceLightStatusBars = isBlackStatusBarTextColor
            }
            if (isBlackNavigationBarTextColor != null) {
                isAppearanceLightNavigationBars = isBlackNavigationBarTextColor
            }
        }
    }
}

我们经常用上述代码做沉浸式。就是程序在statusBar之下,也可以在navigationBar之下,更加延展。
这里其实有几个注意点:
如果ViewCompat.setOnApplyWindowInsetsListener,不调用WindowCompat.setDecorFitsSystemWindows(this, false) 对于constraintLayout是不会回调的,它默认fitsystemWindow。所以这个监听是不靠谱的。

所以使用注意事项:

  1. 如果做了沉浸式,即调用了WindowCompat.setDecorFitsSystemWindows(this, false) + setOnApplyWindowInsetsListener,则可以在回调里面拿到:

    //navBarHeight
    val bottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom 
    //statusbarHeight
    val top = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top 
    
  2. 你如果没有做沉浸式或者setOnApplyWindowInsetsListener不生效,那么调用currentStatusBarAndNavBarHeight()即,ViewCompat.getRootWindowInsets(window.decorView),如果明确已经准备好,则无需post了。也就是在onCreate,onResume使用该函数必须post。
    他的说明文档,写了,必须在view Attach到屏幕上。有兴趣可以研究View的post逻辑源码。

  3. 唯一缺点就是,不能动态监听。这也不能说是缺点,他本意就是当前状态的获取。使用场景就是这样。如果你要动态监听,还是类似实现透明一样,通过WindowCompat.setDecorFitsSystemWindows(this, false) + setOnApplyWindowInsetsListener监听实现。

其他备注:

windowManager.currentWindowMetrics和MaximumWindowMetrics的区别:
max永远是屏幕大小。
current就是当前窗口,其实也是包含了statusBar和navigationBar的。但是当多窗口模式,或者分屏,比如华为有这个悬浮应用功能,获取到的current就是变化后的。
请添加图片描述

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值