【大道至简】官方兼容到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个函数:

kotlin 复制代码
//随意调用,官方出品最精简,最准确。
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
}

接下来介绍一下:

想要屏幕高宽

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

无需等待界面渲染成功,即在onCreate就可以调用,而且里面已经做了低版本兼容,感谢jetpack window库

获取的就是整个屏幕的高度。包含了statusBar,navigationBar的高度一起。与adb shell wm size一致。

这个方法100%可靠。虽然我们看api上描述说低版本(由于navigationBar可能获取不到)近似值,但也是最接近最合理的值,不会是0的。所以这个函数就是目前集大成者。

点击computeCurrentWindowMetrics进入查看源码:

kt 复制代码
 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),来获取屏幕的高宽。官方出品最精简,最准确。

想要SystembarHeight和navBarHeight

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

kt 复制代码
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之下。更加延展。

这里其实有2个注意点:

这个函数如果不调用
WindowCompat.setDecorFitsSystemWindows(this, false) 对于constraintLayout是不会回调的。他默认fitsystemWindow。

我们如果仅仅ViewCompat.setOnApplyWindowInsetsListener是不一定有回调的。

所以使用注意事项:

  1. 你如果做了沉浸式,即调用了WindowCompat.setDecorFitsSystemWindows(this, false) + setOnApplyWindowInsetsListener,则可以在回调监听获取
kt 复制代码
        val bottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom //navBarHeight
        val top = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top //statusbarHeight
  1. 你如果没有做沉浸式或者setOnApplyWindowInsetsListener不生效,那么调用currentStatusBarAndNavBarHeight().

    即,ViewCompat.getRootWindowInsets(window.decorView)

    他的说明文档,写了,必须在view Attach到屏幕上,即view.post的执行,就是attach之后运行的。有兴趣可以研究View的post逻辑源码。

    如果明确已经准备好,则无需post了。也就是在onCreate,onResume使用该函数必须post。

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

其他备注:

windowManager.currentWindowMetrics和MaximumWindowMetrics的区别:

max永远是屏幕大小。

current就是当前窗口,其实也是包含了statusBar和navigationBar的。但是当多窗口模式,或者分屏,比如华为有这个悬浮应用功能,获取到的current就是变化后的。

相关推荐
曙曙学编程2 分钟前
初级数据结构——树
android·java·数据结构
闲暇部落2 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX4 小时前
Android 分区相关介绍
android
大白要努力!5 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee5 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood5 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-8 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen11 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年18 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿20 小时前
会员等级经验问题
android·开发语言·前端·javascript·php