确保您的界面适用于窗口边衬区

当 activity 接管所有内边距的处理后,您可以使用 Compose API 确保内容不会被遮挡,并且可交互元素不会与系统界面重叠。这些 API 还会根据内边距更改同步应用的布局。

例如,以下是最基本的将内嵌应用于整个应用内容的方法:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

此代码段会将 safeDrawing 窗口内边距应用为应用的整个内容周围的内边距。虽然这可确保可交互元素不会与系统界面重叠,但也意味着任何应用都不会在系统界面后面绘制,以实现边到边效果。为了充分利用整个窗口,您需要逐个屏幕或逐个组件微调边衬区的应用位置。

所有这些内嵌类型都会自动呈现动画效果,并且 IME 动画会向 API 21 进行向后移植。因此,使用这些内边距的所有布局也会随着内边距值的变化而自动呈现动画效果。

使用这些内边距类型调整可组合项布局的方法主要有两种:内边距修饰符和内边距大小修饰符。

内边距修饰符

Modifier.windowInsetsPadding(windowInsets: WindowInsets) 会将给定的窗口边衬区应用为内边距,其行为与 Modifier.padding 相同。例如,Modifier.windowInsetsPadding(WindowInsets.safeDrawing) 会将安全绘制边衬区作为内边距应用于所有 4 个边。

此外,还有一些适用于最常见边衬区类型的内置实用程序方法。Modifier.safeDrawingPadding() 就是其中一种方法,相当于 Modifier.windowInsetsPadding(WindowInsets.safeDrawing)。其他内边距类型也有类似的修饰符。

内嵌尺寸修饰符

以下修饰符通过将组件的大小设置为边衬的大小来应用窗口边衬的大小:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

将 windowInsets 的起始边应用为宽度(如 Modifier.width

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

将 windowInsets 的端边应用为宽度(如 Modifier.width

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

将 windowInsets 的顶部边应用为高度(如 Modifier.height

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

将 windowInsets 的底边应用为高度(如 Modifier.height

这些修饰符对于调整占据内边距空间的 Spacer 的大小特别有用:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

插入式广告消耗

内边距内边距修饰符(windowInsetsPaddingsafeDrawingPadding 等辅助程序)会自动使用作为内边距应用的内边距部分。在深入组合树时,嵌套的边衬区内边距修饰符和边衬区大小修饰符会知道边衬区的某些部分已被外部边衬区内边距修饰符使用,并避免重复使用边衬区的同一部分,因为这会导致多余空间过多。

如果边衬区已被使用,边衬区大小修饰符还会避免重复使用边衬区的同一部分。不过,由于它们会直接更改自己的大小,因此不会自行使用内边距。

因此,嵌套内边距修饰符会自动更改应用于每个可组合项的内边距量。

我们再来看一下之前的 LazyColumn 示例,LazyColumn 正在通过 imePadding 修饰符调整大小。在 LazyColumn 中,最后一个项的大小为系统栏底部的高度:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

当 IME 处于关闭状态时,imePadding() 修饰符不会应用内边距,因为 IME 没有高度。由于 imePadding() 修饰符不会应用内边距,因此不会占用任何内边距,并且 Spacer 的高度将与系统栏底部的大小相同。

当 IME 打开时,IME 内嵌动画会随 IME 的大小而变化,并且 imePadding() 修饰符会在 IME 打开时开始应用底部内边距以调整 LazyColumn 的大小。当 imePadding() 修饰符开始应用底部内边距时,它也会开始消耗相应数量的内边距。因此,由于 imePadding() 修饰符已应用系统栏的部分间距,因此 Spacer 的高度开始缩减。一旦 imePadding() 修饰符应用的底部内边距大于系统栏,Spacer 的高度就会为零。

当 IME 关闭时,相应更改会以相反的方向发生:当 imePadding() 应用的高度小于系统栏底部时,Spacer 会从零高度开始扩展,直到 IME 完全动画化退出后,Spacer 最终与系统栏底部的高度一致。

图 2. 使用 TextField 实现端到端延迟列。

此行为是通过所有 windowInsetsPadding 修饰符之间的通信实现的,并且可能会受到其他几种方式的影响。

Modifier.consumeWindowInsets(insets: WindowInsets) 也采用与 Modifier.windowInsetsPadding 相同的方式使用边衬区,但不会将所用边衬区应用为内边距。将此修饰符与内边距大小修饰符结合使用非常有用,可向同级兄弟元素表明系统已使用了一定数量的内边距:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) 的行为与包含 WindowInsets 参数的版本非常相似,但采用任意 PaddingValues 进行使用。当内边距或间距由其他机制(而非内边距修饰符)提供时,这对于告知子项非常有用,例如普通的 Modifier.padding 或固定高度的间隔符:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

如果需要未消耗的原始窗口边衬区,请直接使用 WindowInsets 值,或使用 WindowInsets.asPaddingValues() 返回不受消耗影响的边衬区的 PaddingValues。不过,由于存在以下注意事项,请尽可能使用窗口边衬区内边距修饰符和窗口边衬区尺寸修饰符。

边距和 Jetpack Compose 阶段

Compose 使用底层 AndroidX 核心 API 更新和为边衬动画添加效果,这些 API 使用底层平台 API 来管理边衬。由于这种平台行为,内嵌与 Jetpack Compose 的各个阶段之间存在特殊关系。

内边距的值会在组合阶段之后,但在布局阶段之前更新。这意味着,在组合中读取边衬区的值通常会使用延迟一帧的边衬区值。本页介绍的内置修饰符旨在延迟使用内边距值,直到布局阶段,以确保内边距值在更新时在同一帧上使用。