设置窗口内边距

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

  1. 将目标 API 级别设置为 Android 15 (API 级别 35) 或更高版本,以强制执行全屏显示在 Android 15 及更高版本上。您的应用会显示在系统 UI 后方。您可以通过处理内边距来调整应用的 UI。
  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 来确保内容不会被遮挡,并且可互动元素不会与系统 UI 重叠。这些 API 还会将您的应用布局与内边距更改同步。

例如,这是将内边距应用于整个应用内容的最基本方法

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

    enableEdgeToEdge()

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

此代码段会将safeDrawing窗口内边距作为填充应用于应用的整个内容周围。这样做可以确保可互动元素不会与系统 UI 重叠,但这也意味着应用不会在系统 UI 后面绘制以实现全屏效果。要充分利用整个窗口,您需要逐个屏幕或逐个组件地微调内边距的应用位置。

所有这些内边距类型都会通过回溯到 API 21 的 IME 动画自动实现动画效果。因此,所有使用这些内边距的布局也会随着内边距值的变化而自动进行动画处理。

有两种主要方式可以使用这些内边距类型来调整您的 Composable 布局:填充修饰符和内边距尺寸修饰符。

填充修饰符

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的大小特别有用,该 Spacer 会占用内边距的空间

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

内边距消耗

内边距填充修饰符(windowInsetsPadding以及safeDrawingPadding等辅助函数)会自动消耗作为填充应用的内边距部分。在深入组合树时,嵌套的内边距填充修饰符和内边距尺寸修饰符知道内边距的某些部分已被外部内边距填充修饰符消耗,并避免多次使用相同的内边距部分,从而避免产生过多的额外空间。

如果内边距已被消耗,内边距尺寸修饰符也会避免多次使用相同的内边距部分。但是,由于它们直接更改其尺寸,因此它们本身不消耗内边距。

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

回顾之前的LazyColumn示例,LazyColumn正在被imePadding修饰符调整大小。在LazyColumn中,最后一个项目的大小被设置为系统栏底部的高度。

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

当 IME 关闭时,imePadding()修饰符不应用任何填充,因为 IME 没有高度。由于imePadding()修饰符没有应用填充,因此没有消耗内边距,并且Spacer的高度将是系统栏底部的大小。

当 IME 打开时,IME 内边距会进行动画处理以匹配 IME 的大小,并且imePadding()修饰符开始应用底部填充以调整LazyColumn的大小,随着 IME 的打开。随着imePadding()修饰符开始应用底部填充,它也开始消耗该量的内边距。因此,Spacer的高度开始减小,因为系统栏的部分间距已经由imePadding()修饰符应用。一旦imePadding()修饰符应用的底部填充量大于系统栏时,Spacer的高度为零。

当 IME 关闭时,变化会反向发生:Spacer一旦imePadding()应用的填充小于系统栏的底部时,就开始从零高度展开,直到Spacer在 IME 完全动画关闭后,高度最终与系统栏底部的相同。

图 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或固定高度的 Spacer)提供时,此功能可用于通知子项。

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 的阶段有着特殊关系。

内边距的值在组合阶段之后但在布局阶段之前更新。这意味着在组合阶段读取内边距值通常会使用延迟一帧的内边距值。本页面中描述的内置修饰符旨在将内边距值的使用延迟到布局阶段,这可确保内边距值与更新它们在同一帧中被使用。