WindowInsets 是 Jetpack Compose 中的标准 API,用于处理部分或全部被系统界面遮挡的屏幕区域。这些区域包括状态栏、导航栏和屏幕键盘。您也可以将预定义的 WindowInsetsRulers(例如 SafeDrawing)传递给 Modifier.fitInside 或 Modifier.fitOutside,以使内容与系统栏和刘海屏对齐,或者创建自定义 WindowInsetsRulers。
WindowInsetsRulers 的优势
- 避免使用复杂性:它在布局的放置阶段
运行。这意味着它完全绕过了边衬区使用链,并且始终可以提供系统栏和显示屏凹口的正确绝对位置,无论父布局执行了什么操作。当祖先可组合项错误地使用插页式广告时,使用
Modifier.fitInside或Modifier.fitOutside方法有助于解决问题。 - 轻松避开系统栏:它有助于您的应用内容避开系统栏
和刘海屏,并且比直接使用
WindowInsets更简单。 - 高度可自定义:开发者可以将内容与自定义标尺对齐,并 通过自定义布局精确控制布局。
WindowInsetsRulers 的缺点
- 无法用于测量:由于它在放置 阶段运行,因此它提供的位置信息在之前的 测量阶段不可用。
使用修饰符方法对齐内容
Modifier.fitInside 允许应用将内容与系统栏和显示屏凹口对齐。它可以替代 WindowInsets。Modifier.fitOutside 通常是 Modifier.fitInside 的反向。
例如,如需验证应用内容是否避开了系统栏和显示屏凹口,您可以使用 fitInside(WindowInsetsRulers.safeDrawing.current)。
@Composable fun FitInsideDemo(modifier: Modifier) { Box( modifier = modifier .fillMaxSize() // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc... .fitInside(WindowInsetsRulers.SafeDrawing.current) ) }
下表显示了使用预定义标尺(使用 Modifier.fitInside 或 Modifier.fitOutside)时应用内容的外观。
| 预定义标尺类型 | ||
|---|---|---|
![]() |
![]() |
|
![]() |
不适用 |
|
![]() |
![]() |
|
![]() |
不适用(请改用 |
|
![]() |
![]() |
使用 Modifier.fitInside 和 Modifier.fitOutside 需要对可组合项进行约束。这意味着您必须定义 Modifier.size 或 Modifier.fillMaxSize 等修饰符。
某些标尺(例如 SafeDrawing 和 SystemBars 上的 Modifier.fitOutside)会返回多个标尺。在这种情况下,Android 会使用一个标尺从左、上、右、下放置可组合项。
使用 Modifier.fitInside 避开 IME
如需使用 Modifier.fitInside 处理带有 IME 的底部元素,请传入一个 RectRuler,该标尺采用 NavigationBar 和 Ime 的最内层值。
@Composable fun FitInsideWithImeDemo(modifier: Modifier) { Box( modifier = modifier .fillMaxSize() .fitInside( RectRulers.innermostOf( WindowInsetsRulers.NavigationBars.current, WindowInsetsRulers.Ime.current ) ) ) { TextField( value = "Demo IME Insets", onValueChange = {}, modifier = modifier.align(Alignment.BottomStart).fillMaxWidth() ) } }
使用 Modifier.fitInside 避开状态栏和标题栏
同样,如需验证顶部元素是否使用 Modifier.fitInsider 避开了状态栏和标题栏,请传入一个 RectRuler,该标尺采用 StatusBars 和 CaptionBar 的最内层值。
@Composable fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) { Box( modifier = modifier .fillMaxSize() .fitInside( RectRulers.innermostOf( WindowInsetsRulers.StatusBars.current, WindowInsetsRulers.CaptionBar.current ) ) ) }
创建自定义 WindowInsetsRulers
您可以将内容与自定义标尺对齐。例如,假设存在这样一种用例:父可组合项不正确地处理插页式广告,导致下游子项出现内边距问题。虽然可以通过其他方式(包括使用 Modifier.fitInside)解决此问题,但您也可以创建自定义标尺来精确对齐子可组合项,而无需在上游父项中修复此问题,如以下示例和视频所示:
@Composable fun WindowInsetsRulersDemo(modifier: Modifier) { Box( contentAlignment = BottomCenter, modifier = modifier .fillMaxSize() // The mistake that causes issues downstream, as .padding doesn't consume insets. // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars), // assume it's difficult to identify this issue to see how WindowInsetsRulers can help. .padding(WindowInsets.navigationBars.asPaddingValues()) ) { TextField( value = "Demo IME Insets", onValueChange = {}, modifier = modifier // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child // Composable without having to fix the parent upstream. .alignToSafeDrawing() // .imePadding() // .fillMaxWidth() ) } } fun Modifier.alignToSafeDrawing(): Modifier { return layout { measurable, constraints -> if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) { val placeable = measurable.measure(constraints) val width = placeable.width val height = placeable.height layout(width, height) { val bottom = WindowInsetsRulers.SafeDrawing.current.bottom .current(0f).roundToInt() - height val right = WindowInsetsRulers.SafeDrawing.current.right .current(0f).roundToInt() val left = WindowInsetsRulers.SafeDrawing.current.left .current(0f).roundToInt() measurable.measure(Constraints.fixed(right - left, height)) .place(left, bottom) } } else { val placeable = measurable.measure(constraints) layout(placeable.width, placeable.height) { placeable.place(0, 0) } } } }
以下视频展示了一个示例,其中左侧图片中的上游父项导致了有问题的 IME 边衬区消耗,而右侧图片中使用了自定义标尺来解决此问题。由于父项未使用导航栏内边距,因此 TextField 可组合项下方显示了额外的内边距。如前面的代码示例所示,子项使用自定义标尺放置在右侧图片中的正确位置。
验证父项是否受到约束
为了安全地使用 WindowInsetsRulers,请确保父项提供有效的约束条件。父项必须具有已定义的大小,并且不能依赖于使用 WindowInsetsRulers 的子项的大小。在父可组合项上使用 fillMaxSize 或其他大小修饰符。
同样,将使用 WindowInsetsRulers 的可组合项放置在滚动容器(例如 verticalScroll)内可能会导致意外行为,因为滚动容器提供无界高度约束,这与标尺的逻辑不兼容。







