设置窗口边衬区

当您的 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。此外,随着边衬区值的变化,使用这些边衬区的所有布局也会自动添加动画效果。

您可以通过以下三种方式处理 Inset,以调整可组合项布局:

内边距修饰符

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

此外,还有一些针对最常见边衬区类型的内置实用方法。 Modifier.safeDrawingPadding() 就是这样一种方法,相当于 Modifier.windowInsetsPadding(WindowInsets.safeDrawing)。下表列出了内置内边距修饰符以及每个修饰符应用的 WindowInsets 类型:

Modifier.safeDrawingPadding()

WindowInsets.safeDrawing

Modifier.safeGesturesPadding()

WindowInsets.safeGestures

Modifier.safeContentPadding()

WindowInsets.safeContent

Modifier.statusBarsPadding()

WindowInsets.statusBars

Modifier.navigationBarsPadding()

WindowInsets.navigationBars

Modifier.captionBarPadding()

WindowInsets.captionBar

Modifier.systemBarsPadding()

WindowInsets.systemBars

Modifier.imePadding()

WindowInsets.ime

Modifier.systemGesturesPadding()

WindowInsets.systemGestures

Modifier.mandatorySystemGesturesPadding()

WindowInsets.mandatorySystemGestures

Modifier.displayCutoutPadding()

WindowInsets.displayCutout

Modifier.waterfallPadding()

WindowInsets.waterfall

如需了解每种边衬区类型涵盖的内容,请参阅窗口边衬区简介。

边衬区大小修饰符

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

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
            )
        )
    }
}

边衬区消耗

边衬区内边距修饰符(windowInsetsPadding 和 safeDrawingPadding 等辅助程序)会自动使用作为内边距应用的边衬区部分。在深入到组合树中时,嵌套的边衬区内边距修饰符和边衬区大小修饰符知道,部分边衬区已被外部边衬区内边距修饰符使用,因此会避免多次使用同一部分边衬区,否则会导致额外的空间过多。

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

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

查看与之前相同的 LazyColumn 示例,LazyColumn 正在通过 imePadding 修饰符调整大小。在 LazyColumn 中,最后一项的大小设置为系统栏底部的高度:

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

当 IME 关闭时,imePadding() 不会应用任何内边距。因此,不会使用任何边衬区,并且 Spacer 高度与底部系统栏匹配。

当 IME 打开时,IME 边衬区会以动画形式调整大小,以匹配 IME 的大小,并且 imePadding() 修饰符会开始应用底部内边距,以便在 IME 打开时调整 LazyColumn 的大小。当 imePadding() 修饰符开始应用底部内边距时,它也会开始使用该内边距量。因此,Spacer 的高度开始减小,因为 imePadding() 修饰符已应用了系统栏的部分间距。一旦 imePadding() 修饰符应用的底部内边距大于系统栏,Spacer 的高度就会为零。

当 IME 关闭时,系统会反向执行这些更改:Spacer 从零高度开始展开,因为 imePadding() 应用的高度小于底部系统栏的高度。 最后,在 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 值,或使用 WindowInsets.asPaddingValues() 返回不受使用影响的边衬区的 PaddingValues。不过,由于存在以下注意事项,请尽可能使用窗口边衬区内边距修饰符和窗口边衬区大小修饰符。

边衬区和 Jetpack Compose 阶段

Compose 使用底层 AndroidX 核心 API 来更新和动画化边衬区,这些 API 使用管理边衬区的底层平台 API。由于该平台行为,边衬区与 Jetpack Compose 的阶段具有特殊关系。

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