FitsSystemWindows=false导致虚拟导航栏遮挡界面的问题
发现问题
偶然在一部有虚拟导航栏的手机上,发现之前写的一个 App 有界面被虚拟导航栏遮挡的问题。
分析
因为使用了沉浸式状态栏,其中有一段这样的代码:
val window = activity.window
val decorView = window.decorView
// false 状态栏覆盖在 window 之上,true 不会覆盖
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(window, decorFitsSystemWindows)
} else {
val decorFitsFlags = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
val sysUiVis = decorView.systemUiVisibility
decorView.systemUiVisibility =
if (decorFitsSystemWindows) sysUiVis and decorFitsFlags.inv() else sysUiVis or decorFitsFlags
}
FitsSystemWindows
这个属性影响的不止 statusBar,还包括 navigationBar。
解决方法
思路是判断是否有虚拟导航栏,然后给根布局设置相应的 margin 值。
于是,在网上找到这样一段代码,用于判断是否有虚拟导航栏:
// 是否有虚拟导航栏
val hasNavBar: Boolean
get() {
var result = false
val context = AppGlobals.application
val resourceId =
context.resources.getIdentifier("config_showNavigationBar", "bool", "android")
if (resourceId > 0) {
result = context.resources.getBoolean(resourceId)
}
return result
}
经过测试,发现手上的真机 小米10Ultra
以及 虚拟机 Pixal 3a
出现了两种相反的结果
- 在真机
小米10Ultra
上hasNavBar
永远为true
- 在虚拟机
Pixal 3a
上hasNavBar
永远为false
所以,这种方法并不适合。
然后,想到通过显示区域高度与根布局高度是否一致来判断。
// 判断显示区域与根布局高度是否一致
fun hasNavBar(activity: Activity): Boolean {
var flag = false
val content = activity.window.decorView.findViewById<View>(android.R.id.content)
if (null != content) {
val wm = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activity.display
} else {
wm.defaultDisplay
}
display?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val bounds = wm.currentWindowMetrics.bounds
if (!isLandscape()) {
if (content.bottom != bounds.bottom) {
flag = true
}
} else {
if (content.right != bounds.bottom) {
flag = true
}
}
} else {
val point = Point()
display.getRealSize(point)
if (!isLandscape()) {
if (content.bottom != point.y) {
flag = true
}
} else {
if (content.right != point.y) {
flag = true
}
}
}
}
}
return flag
}
// 判断是否是横屏
fun isLandscape(): Boolean {
return AppGlobals.application.resources
.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
// 虚拟导航栏高度
val navBarHeight: Int
get() {
var result = 0
val context = AppGlobals.application
val resourceId =
context.resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
result = context.resources.getDimensionPixelSize(resourceId)
}
return result
}
发现,这种方法在 虚拟机 Pixal 3a
上是可行的。
但是在真机 小米10Ultra
上,却是依然永远返回 true。且在不显示虚拟导航栏的情况下,导航栏的高度返回为44px,即以下截图所示底部白边。
于是想到,44px的虚拟导航栏是没有意义的,所以为了适配这部机,多加个虚拟导航栏最小高度的判断。
fun hasNavBar(activity: Activity): Boolean {
var flag = false
val content = activity.window.decorView.findViewById<View>(android.R.id.content)
if (null != content) {
val wm = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activity.display
} else {
wm.defaultDisplay
}
display?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val bounds = wm.currentWindowMetrics.bounds
if (!isLandscape()) {
if (content.bottom != bounds.bottom) {
flag = true
}
} else {
if (content.right != bounds.bottom) {
flag = true
}
}
} else {
val point = Point()
display.getRealSize(point)
if (!isLandscape()) {
if (content.bottom != point.y) {
flag = true
}
} else {
if (content.right != point.y) {
flag = true
}
}
}
}
}
return flag && navBarHeight > 50 // 小米10至尊版 flag 永远为true,有个最小的 navBarHeight=44
}
至此,这个方法在2部机上都可以正常显示。