1. 从“被遮挡”到“和谐共存”:理解WindowInset的本质
如果你做过Android开发,尤其是处理过全屏、沉浸式体验,或者遇到过输入法弹起时布局被顶得一塌糊涂的情况,那你一定对“边衬区”这个概念不陌生。在Android 12里,系统对这块的管理变得更精细、也更强大。今天我们不聊那些晦涩的源码,就从一个开发者的实战视角,聊聊怎么用好WindowInset,让你应用的内容和系统的UI(状态栏、导航栏、输入法)能和平共处,甚至打造出丝滑的沉浸式体验。
简单来说,WindowInset(窗口边衬区)就是系统UI“霸占”掉的那部分屏幕区域。想象一下你的Activity是一张画布,但系统说:“嘿,顶部这66像素是我的状态栏地盘,底部这132像素是我的导航栏地盘,输入法来了还要占一大块。”这些被“霸占”的区域,就是Inset。你的内容如果傻乎乎地画到这些区域里,要么被状态栏遮住一半,要么和导航栏手势冲突,体验非常糟糕。
在Android 12之前,我们处理这些问题,常常是和WindowManager.LayoutParams的FLAG_系列标志位,或者View的setSystemUiVisibility方法斗智斗勇。代码写起来像在变魔术,而且不同系统版本行为还不一致,经常要写一堆兼容性代码。从Android 11(API 30)开始,Google引入了新的WindowInsetsController API,到了Android 12这套API更加成熟稳定。它的核心思想是,把系统UI的可见性和行为控制,以及它们对应用布局的影响(即Inset),进行更清晰、更声明式的管理。
原始文章里那一大段dumpsys输出的InsetsState,其实就是系统内部对所有Inset来源(InsetsSource)的一个快照。我们不用深究每一行,但要知道关键信息:比如ITYPE_STATUS_BAR对应状态栏,它的frame是[0,0][1080,66],表示它占据了屏幕顶部高66像素的区域;ITYPE_NAVIGATION_BAR对应导航栏,在底部。还有ITYPE_IME代表输入法,当它visible=false时frame是全零,一旦弹出就会变成具体的区域。理解这些类型,是我们进行精确控制的基础。
2. 新API实战:WindowInsetsController的入门与进阶
拿到WindowInsetsController对象非常简单,通常从View或者Window的DecorView获取:
// Kotlin 方式,在Activity中
val windowInsetsController = window.decorView.windowInsetsController
// 或者从任意View获取(需要确保View已attached到窗口)
view.windowInsetsController?.hide(WindowInsets.Type.statusBars())
2.1 基础操作:显示与隐藏系统栏
最常用的就是控制状态栏和导航栏的显示隐藏,来实现全屏效果。
// 隐藏状态栏和导航栏(沉浸式模式)
windowInsetsController?.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
// 重新显示状态栏和导航栏
windowInsetsController?.show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
这里有个非常重要的行为变化需要注意:在旧API中,隐藏导航栏后,手指从屏幕底部上滑,导航栏会临时显示(即“粘性沉浸”模式)。而使用新的hide()方法,默认是非粘性的。也就是说,用户从底部上滑,导航栏不会自动弹出,这给了我们实现完全自定义手势的空间。如果你希望恢复旧版的粘性行为,需要设置一个标志位:
windowInsetsController?.let { controller ->
// 先隐藏
controller.hide(


4615

被折叠的 条评论
为什么被折叠?



