【大道至简】官方兼容到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就是变化后的。

相关推荐
百锦再41 分钟前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗1 小时前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO1 小时前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下2 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu5 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
凉、介6 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci