WindowInsets
是 Jetpack Compose 中的标准 API,用于处理部分或完全被系统界面遮挡的屏幕区域。这些区域包括状态栏、导航栏和屏幕键盘。您也可以传递预定义的 WindowInsetsRulers
(例如 SafeDrawing
)给 Modifier.fitInside
或 Modifier.fitOutside
,以使内容与系统栏和刘海屏对齐,或者创建自定义 WindowInsetsRulers
。
WindowInsetsRulers
的优势
- 避免了消费复杂性:它在布局的放置阶段运行。这意味着,它完全绕过了插边消耗链,并且无论父布局做了什么,都可以始终提供系统栏和显示屏刘海的正确绝对位置。当祖先可组合项错误地使用边衬区时,使用
Modifier.fitInside
或Modifier.fitOutside
方法有助于解决问题。 - 轻松避开系统栏:有助于应用内容避开系统栏和刘海屏,并且比直接使用
WindowInsets
更简单。 - 高度可自定义:开发者可以将内容与自定义标尺对齐,并通过自定义布局精确控制布局。
WindowInsetsRulers
的缺点
- 无法用于衡量:由于它在展示位置阶段运行,因此在之前的衡量阶段,它提供的位置信息是不可用的。
- 可能导致布局不稳定:如果父布局的大小取决于其子布局的大小,则可能会导致崩溃。由于使用
WindowInsetsRulers
的子项在放置期间可能会更改其位置或大小,因此可能会导致布局循环不稳定。
创建自定义 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
等滚动容器内可能会导致意外行为,因为滚动容器提供的是无界高度限制,这与标尺的逻辑不兼容。