参考: 传送门
能够造成SystemUI Flag被系统自动清除的交互分类
作用是隐藏系统NavigationBar。
但是用户的任何交互,都会导致此Flag被系统清除,进而导航栏自动重新显示,同时View.SYSTEM_UI_FLAG_FULLSCREEN也会被自动清除,因此StatusBar也会同时显示出来。
作用是隐藏StatusBar。
和WindowManager.LayoutParams.FLAG_FULLSCREEN有相同视觉效果。不同在于,此Flag一般用在暂时需要全屏的情形(如:阅读应用,全屏视频等),以便让用户的注意力暂时集中在内容上,而如果只是简单的需要一直停留在全屏状态(如:游戏应用),使用WindowManager.LayoutParams.FLAG_FULLSCREEN则是更好的选择。
此Flag会因为各种的交互(如:跳转到其他应用,下拉StatusBar,弹出键盘)的发送而被系统清除。
作用:避免某些用户交互造成系统自动清除全屏状态。
作用:避免某些用户交互造成系统自动清除全屏状态。同时Activity的部分内容也因此被StatusBar覆盖遮挡。
作用:在不隐藏导航栏的情况下,将Activity的显示范围扩展到导航栏底部。同时Activity的部分内容也因此被NavigationBar覆盖遮挡。
作用:在不隐藏StatusBar的情况下,将view所在window的显示范围扩展到StatusBar下面。同时Activity的部分内容也因此被StatusBar覆盖遮挡。
作用: 稳定布局。当StatusBar和NavigationBar动态显示和隐藏时,系统为fitSystemWindow=true的view设置的padding大小都不会变化,所以view的内容的位置也不会发生移动。
上面的参数常量,大部分都会提示已经过期了,因为在API 30 版本里面出了WindowInsetsControllerCompat 的一个新类。
比如 View.SYSTEM_UI_FLAG_FULLSCREEN 以及 WindowManager.LayoutParams.FLAG_FULLSCREEN 常量点进去都会提示使用:
但是以上两个常量的作用并不一样。 View.SYSTEM_UI_FLAG_FULLSCREEN 的隐藏作用在下拉状态栏之后就会取消隐藏。需要搭配 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 来位置隐藏的效果。 而查看 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 源码又会提示使用:
经过验证也得出 WindowInsetsController#hide(int) 方法的隐藏会被下拉状态栏操作取消。需要设置 controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 来维持隐藏的状态。
隐藏状态栏分两种情况,一个是在非刘海屏或挖孔屏,也就是正常的直面屏上。
<item name="android:windowFullscreen">trueitem>
//第二种
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
//第三种
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN
WindowInsetsControllerCompat(window, window.decorView).let {
it.hide(WindowInsetsCompat.Type.navigationBars())
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
用 WindowInsetsController方式实现:
WindowInsetsControllerCompat(window, window.decorView).let {
it.hide(WindowInsetsCompat.Type.navigationBars())
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}


可以看到上面有一条很粗的黑边。因为那是前摄挖孔所在,默认显示内容是不能占用挖孔的地方的。
而google 官方是有针对刘海屏的适配方案的,简单的说就是可以让显示内容上移到刘海状态栏的位置。有三种方式供选择:
//默认情况,全屏页面不可用刘海区域,非全屏页面可以进行使用
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
//不允许使用刘海区域
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
//允许页面延伸到刘海区域
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1;
使用方式如下:
if (Build.VERSION.SDK_INT >= 28) {
val lp = window.attributes
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = lp
}
但是加上这个只是允许window 可以上升到刘海屏的状态栏,还需要加上 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 来确保view可以上移到状态栏。具体如下
fun hideStatusBar(window: Window) {
//直屏手机一行就搞定
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
//允许window 的内容可以上移到刘海屏状态栏
if (Build.VERSION.SDK_INT >= 28) {
val lp = window.attributes
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = lp
}
//将显示内容上移到状态栏
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
用 WindowInsetsController方式实现:
/**
* 隐藏状态栏,显示内容上移到状态栏
*/
fun hideStatusBar3(window: Window) {
WindowInsetsControllerCompat(window, window.decorView).let {
//效果同SYSTEM_UI_FLAG_FULLSCREEN
it.hide(WindowInsetsCompat.Type.statusBars())
//维持隐藏的效果,hide 方法的隐藏会被下拉状态栏取消的。效果同 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
it.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
//允许window 的内容可以上移到刘海屏状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val lp = window.attributes
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = lp
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//将显示内容上移
// window.setDecorFitsSystemWindows(false)
// } else {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// }
}
}
此处 View.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 之所以不用 window.setDecorFitsSystemWindows(false) 代替,是因为 window.setDecorFitsSystemWindows(false) 还包含有 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION的作用。如果替代了,则效果如下:

可以看到底部的两行textview都被遮挡了。也就是页面内容延伸到了导航栏底部。
沉浸式状态栏就很容易理解了,就是把显示的内容上移到状态栏,但是又不隐藏状态栏。(当然直接修改状态栏的颜色,让它和背景一样也是可以的,但是这种仅仅在纯色的情况下实用,如果是图片类的背景就不适用了)。实现如下:
/**
* 沉浸式状态栏
*/
fun transparentStatusBar(window: Window){
//去掉半透明的可能性
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
//可以设置系统栏的背景色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT
//view的位置上移到系统栏
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
看下效果图:

效果是实现了,但是有点小瑕疵,我们是不希望状态栏遮住我们的内容,但是如果每个页面都要手动计算状态栏的高度然后去设置padding 就过于麻烦了。很简单,在布局文件的根布局加上:
android:fitsSystemWindows="true"
这个属性只有设置了 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 或View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,才会有效果。
作用呢就是自动给view加上个对应系统栏高度的padding。看下效果:

没啥好说的,直接上代码吧
/**
* 隐藏导航栏
*/
fun hideNavigation(window: Window) {
//将显示内容下移到导航栏
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
用 WindowInsetsController方式实现:
/**
* 隐藏导航栏
*/
fun hideNavigation2(window: Window) {
//将显示内容下移到导航栏
WindowInsetsControllerCompat(window, window.decorView).let {
//效果同SYSTEM_UI_FLAG_HIDE_NAVIGATION
it.hide(WindowInsetsCompat.Type.navigationBars())
//维持隐藏的效果,hide 方法的隐藏会被下拉状态栏取消的。效果同 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
it.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
/**
* 沉浸式导航栏,这里就不用WindowInsetsControllerCompat 实现了,因为View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 没办法等效替换
* fixme :有BUG, View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 不仅仅将内容下移到导航栏,还会上移到状态栏
*/
fun transparentNavigationBar(window: Window) {
//去掉半透明的可能性
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
//可以设置系统栏的背景色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.navigationBarColor = Color.TRANSPARENT
//view的位置下移到导航栏
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
记得要配合 fitsSystemWindows 使用

如果同时使用上面的两个方法组合是不行的。因为 window.decorView.systemUiVisibility 是赋值使用的,后一个会覆盖前一个的。实现如下:
/**
* 隐藏两个系统栏
*/
fun hideSystemBar(window: Window){
//直屏手机一行就搞定
window.decorView.systemUiVisibility =
// 隐藏系统栏
View.SYSTEM_UI_FLAG_FULLSCREEN or
// 隐藏导航栏
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
//维持隐藏的效果
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
//将内容往上推到刘海屏的位置
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
//允许window 的内容可以上移到刘海屏状态栏
if (Build.VERSION.SDK_INT >= 28) {
val lp = window.attributes
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = lp
}
}
用 WindowInsetsController方式实现:
/**
* 隐藏两个系统栏
*/
fun hideSystemBar2(window: Window) {
WindowInsetsControllerCompat(window, window.decorView).let {
//效果同SYSTEM_UI_FLAG_FULLSCREEN
it.hide(WindowInsetsCompat.Type.statusBars())
it.hide(WindowInsetsCompat.Type.navigationBars())
//维持隐藏的效果,hide 方法的隐藏会被下拉状态栏取消的。效果同 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
it.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
//允许window 的内容可以上移到刘海屏状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val lp = window.attributes
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = lp
//在不隐藏系统栏的情况下,将内容移动到系统栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//该方法会同时作用于状态栏以及导航栏
window.setDecorFitsSystemWindows(false)
} else {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
}

/**
* 沉浸式状态栏以及导航栏
*/
fun transparentSystemBar(window: Window) {
//去掉半透明的可能性
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
//可以设置系统栏的背景色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
//view的位置上移到系统栏
window.decorView.systemUiVisibility =
//将内容上移到状态栏
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
//将内容下移到状态栏
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
/**
* 沉浸式状态栏以及导航栏
*/
fun transparentSystemBar2(window: Window) {
//去掉半透明的可能性
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
//可以设置系统栏的背景色
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
//在不隐藏系统栏的情况下,将内容移动到系统栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//该方法会同时作用于状态栏以及导航栏
window.setDecorFitsSystemWindows(false)
} else {
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
}
}
记得要配合 fitsSystemWindows 使用

