设置窗口边衬区

如需允许应用完全控制其绘制内容的位置,请按以下设置步骤操作。如果不执行这些步骤,您的应用可能会在系统界面后面绘制黑色或纯色,或者无法与软件键盘同步动画。

  1. 以 Android 15(API 级别 35)或更高版本为目标平台,以便在 Android 15 及更高版本上强制执行全屏显示。您的应用显示在系统界面后面。您可以通过处理边衬区来调整应用的界面。
  2. (可选)在 Activity.onCreate() 中调用 enableEdgeToEdge(),以便您的应用在之前的 Android 版本中实现无边框设计。
  3. 在 activity 的 AndroidManifest.xml 条目中设置 android:windowSoftInputMode="adjustResize"。此设置允许您的应用将软件 IME 的大小作为边衬区接收,这有助于您在 IME 在应用中显示和消失时应用适当的布局和内边距。

    <!-- In your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

使用 Compose API

当您的 activity 控制了所有边衬区处理后,您可以使用 Compose API 来确保内容不会被遮挡,并且可互动元素不会与系统界面重叠。这些 API 还会将应用的布局与边衬区变化同步。

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

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

    enableEdgeToEdge()

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

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

所有这些边衬区类型都会自动使用回溯到 API 21 的 IME 动画添加动画效果。相应地,使用这些边衬区的所有布局也会随着边衬区值的变化而自动动画化。

您可以使用以下两种主要方式来调整可组合项布局:内边距修饰符和边衬区大小修饰符。

内边距修饰符

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() 修饰符开始应用底部内边距时,它也会开始消耗相应数量的边衬区。因此,Spacer 的高度开始减小,因为 imePadding() 修饰符已应用系统栏的部分间距。一旦 imePadding() 修饰符应用的底部内边距量大于系统栏,Spacer 的高度就会变为零。

当 IME 关闭时,更改会反向发生:SpacerimePadding() 应用小于系统栏底部的高度时开始从零高度展开,直到 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 的阶段有着特殊的关系。

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