Android 平台负责绘制系统界面,例如状态栏和导航栏。无论 用户使用的应用
WindowInsets
提供有关系统的信息
确保应用在正确的区域绘制且界面不会被遮挡的界面
由系统界面提供
在 Android 14(API 级别 34)及更低版本中,应用界面不会在其底层绘制 系统栏和刘海屏
在 Android 15(API 级别 35)及更高版本中,应用会在系统下绘制 当您的应用以 SDK 35 为目标平台后,通知栏和刘海屏。这样,您可以获得 让您的应用能够充分利用 可用的窗口空间
在系统界面后面显示内容称为“无边框”。在本页中,您将了解不同类型的边衬区、如何实现边到边设计,以及如何使用边衬区 API 为界面添加动画效果,并确保应用的内容不会被系统界面元素遮挡。
边衬区基础知识
当一款应用采用无边框格式时,您需要确保应用 互动不会被系统界面遮挡。例如,如果按钮是 放置在导航栏后面,用户可能无法点击。
指定系统界面的大小以及有关其放置位置的信息 通过边衬区实现。
系统界面的每个部分都有相应的边衬区类型, 以及放置位置例如,状态栏内边距提供状态栏的大小和位置,而导航栏内边距提供导航栏的大小和位置。每种类型的内嵌边距都由四个像素尺寸组成:上、左、右和下。这些维度指定了 系统界面从应用窗口的相应侧边扩展。因此,为避免与此类系统界面重叠,应用界面必须内嵌该数量。
您可以通过 WindowInsets
使用以下内置 Android 边衬区类型:
描述状态栏的边衬区。这些是顶部的系统界面栏,其中包含通知图标和其他指示器。 |
|
状态栏内嵌,适用于状态栏处于可见状态时。如果状态栏当前处于隐藏状态(由于进入沉浸式全屏模式),则主状态栏边衬区将为空,但这些边衬区将是非空。 |
|
描述导航栏的边衬区。这些是设备左侧、右侧或底部的系统界面栏,用于描述任务栏或导航图标。这些内容可能会在运行时根据用户的首选导航方法和与任务栏的互动而发生变化。 |
|
导航栏内嵌,适用于导航栏处于可见状态时。如果导航栏当前处于隐藏状态(由于进入沉浸式全屏模式),则主导航栏边衬区将为空,但这些边衬区将是非空。 |
|
描述系统界面窗口装饰(如果是在自由窗口)中的边衬区,例如顶部标题栏。 |
|
字幕栏内嵌,以便在字幕可见时显示。如果字幕栏当前处于隐藏状态,则主字幕栏边衬区将为空,但这些边衬区将是非空的。 |
|
系统栏边衬区的集合,包括状态栏、导航栏和标题栏。 |
|
系统栏插入,用于指明何时可见。如果系统栏当前处于隐藏状态(由于进入沉浸式全屏模式),则主系统栏边衬区将为空,但这些边衬区将是非空。 |
|
描述软件键盘底部所占空间的边衬区。 |
|
描述当前键盘动画之前软件键盘所占空间的边衬区。 |
|
描述当前键盘动画播放完后软件键盘将占据的空间量的边衬区。 |
|
一种边衬区,用于描述有关导航界面的更多详细信息,并提供“点按”空间。将由系统(而非应用)进行处理。对于带有手势导航的透明导航栏,某些应用元素可通过系统导航界面点按。 |
|
可点按的元素在它们可见时插入。如果可点按元素当前处于隐藏状态(由于进入沉浸式全屏模式),则可点按元素的主要边衬区将为空,但这些边衬区将是非空的。 |
|
表示系统将在其中拦截导航手势的边衬区的边衬区。应用可以通过 |
|
系统手势的子集,将始终由系统处理,且无法通过 |
|
内嵌表示为避免与显示屏切口(缺口或针孔)重叠而需的间距量。 |
|
表示瀑布显示屏曲线区域的边衬区。瀑布屏的屏幕边缘有弧形区域,屏幕会从这些区域开始沿着设备侧边展开。 |
这些类型归纳为三个“安全”类型,这些边衬区类型可确保内容 遮挡:
这些“安全”内嵌类型会根据底层平台内嵌方式以不同的方式保护内容:
- 使用
WindowInsets.safeDrawing
保护不应在任何系统界面下绘制的内容。这是内边距最常见的用法:防止绘制被系统界面遮挡(部分或完全遮挡)的内容。 - 使用
WindowInsets.safeGestures
通过手势保护内容。这样可以避免系统手势与应用手势(例如底部动作条、轮播界面或游戏中的手势)发生冲突。 - 将
WindowInsets.safeContent
作为WindowInsets.safeDrawing
和WindowInsets.safeGestures
的组合使用,以确保内容没有视觉重叠和手势重叠。
边衬区设置
如需允许应用完全控制其绘制内容的位置,请遵循以下设置 步骤。如果不执行这些步骤,您的应用可能会在系统界面后面绘制黑色或纯色,或者动画不会与软件键盘同步。
- 以 SDK 35 或更高版本为目标平台,以便在 Android 15 及更高版本上强制执行边到边。您的应用 显示在系统界面后面。您可以通过处理 边衬区。
- (可选)在
Activity.onCreate()
中调用enableEdgeToEdge()
,以便您的应用在旧版 Android 上采用无边框设计。 在您的 Activity 的
android:windowSoftInputMode="adjustResize"
AndroidManifest.xml
个条目。此设置允许您的应用接收 作为边衬区使用,您可以使用这些边衬区来填充和设置内容 。<!-- 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
窗口边衬区作为
应用的全部内容虽然这可确保可交互元素
与系统界面重叠,这也意味着任何应用都不会
来实现全屏效果。为了充分利用整个窗口,您需要逐个屏幕或逐个组件微调边衬区的应用位置。
所有这些边衬区类型均通过 IME 动画自动添加动画效果 已向后移植到 API 21。总而言之,所有使用这些边衬区的布局都是 也会随着边衬区值的变化而自动添加动画效果。
您可以通过两种主要方式使用这些边衬区类型调整可组合项 布局:内边距修饰符和边衬区大小修饰符。
内边距修饰符
Modifier.windowInsetsPadding(windowInsets: WindowInsets)
会应用
指定的窗口边衬区作为内边距,其行为与 Modifier.padding
一样。
例如,Modifier.windowInsetsPadding(WindowInsets.safeDrawing)
适用于
将安全绘图边衬区全部 4 条边设为内边距
此外,还有一些适用于最常见边衬区类型的内置实用程序方法。Modifier.safeDrawingPadding()
就是其中一种方法,相当于 Modifier.windowInsetsPadding(WindowInsets.safeDrawing)
。还有类似的
修饰符。
边衬区大小修饰符
以下修饰符通过将组件的大小设置为边衬的大小来应用窗口边衬的大小:
将 windowInsets 的起始边应用为宽度(如 |
|
将 windowInsets 的末端作为宽度应用(例如 |
|
将 windowInsets 的顶部作为高度应用(如 |
|
|
将 windowInsets 的底边应用为高度(如 |
这些修饰符对于调整占据内边距空间的 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
。当 imePadding()
修饰符开始应用
底部内边距,也会开始消耗相应数量的边衬区。因此,
Spacer
的高度开始减少,作为系统间距的一部分
条形已由 imePadding()
修饰符应用。部署
imePadding()
修饰符会应用更大的底部内边距
则 Spacer
的高度为零。
当 IME 关闭时,相应更改会以相反的方向发生:当 imePadding()
应用的高度小于系统栏底部时,Spacer
会从零高度开始扩展,直到 IME 完全动画化退出后,Spacer
最终与系统栏底部的高度一致。
此行为是通过
windowInsetsPadding
修饰符,并且会受到一些
方法。
Modifier.consumeWindowInsets(insets: WindowInsets)
也会使用边衬区
与 Modifier.windowInsetsPadding
的方法相同,但它不适用于
将已使用的边衬区作为内边距。与边衬区结合使用时,
size 修饰符,用于向同级元素表明特定数量的边衬区
:
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 进行管理。有了这个平台 边衬区与 Jetpack 的各个阶段 写邮件。
边衬区的值在组合阶段之后但在 布局阶段。这意味着,读取组合中边衬区的值 通常使用的边衬区值会晚一帧。本页介绍的内置修饰符旨在延迟使用内边距值,直到布局阶段,以确保内边距值在更新时在同一帧上使用。
使用 WindowInsets
的键盘 IME 动画
您可以将 Modifier.imeNestedScroll()
应用于滚动容器,以打开和
在用户滚动到容器底部时自动关闭输入法。
class WindowInsetsExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContent { MaterialTheme { MyScreen() } } } } @OptIn(ExperimentalLayoutApi::class) @Composable fun MyScreen() { Box { LazyColumn( modifier = Modifier .fillMaxSize() // fill the entire window .imePadding() // padding for the bottom for the IME .imeNestedScroll(), // scroll IME at the bottom content = { } ) FloatingActionButton( modifier = Modifier .align(Alignment.BottomEnd) .padding(16.dp) // normal 16dp of padding for FABs .navigationBarsPadding() // padding for navigation bar .imePadding(), // padding for when IME appears onClick = { } ) { Icon(imageVector = Icons.Filled.Add, contentDescription = "Add") } } }
对 Material 3 组件的边衬区支持
为了便于使用,许多内置的 Material 3 可组合项
(androidx.compose.material3
)
根据可组合项在应用中的放置方式自行处理边衬区
根据 Material 规范
边衬区处理可组合项
下面列出了 Material Design 组件 可自动处理边衬区
应用栏
TopAppBar
/SmallTopAppBar
/CenterAlignedTopAppBar
/MediumTopAppBar
/LargeTopAppBar
:应用系统栏的顶部和水平侧边作为内边距,因为它在窗口顶部使用。BottomAppBar
:应用系统栏的底部和水平侧边作为内边距。
内容容器
ModalDrawerSheet
/DismissibleDrawerSheet
/PermanentDrawerSheet
(模态抽屉式导航栏内的内容):对内容应用垂直和 start 边衬区。ModalBottomSheet
:应用底部内边距。NavigationBar
:应用底部和水平内边距。NavigationRail
:应用垂直和起始内边距。
Scaffold
默认情况下
Scaffold
提供边衬区作为参数 paddingValues
,供您使用和使用。
Scaffold
不会将边衬应用于内容;此责任由您承担。
例如,如需在 Scaffold
内使用 LazyColumn
来使用这些内嵌,请执行以下操作:
Scaffold { innerPadding -> // innerPadding contains inset information for you to use and apply LazyColumn( // consume insets as scaffold doesn't do it by default modifier = Modifier.consumeWindowInsets(innerPadding), contentPadding = innerPadding ) { items(count = 100) { Box( Modifier .fillMaxWidth() .height(50.dp) .background(colors[it % colors.size]) ) } } }
替换默认边衬区
您可以将传递给可组合项的 windowInsets
形参更改为
配置可组合项的行为。此参数可以是不同类型的
window inset 改为应用,或者通过传递空实例来停用:
WindowInsets(0, 0, 0, 0)
。
例如,要停用
LargeTopAppBar
,
将 windowInsets
参数设置为空实例:
LargeTopAppBar( windowInsets = WindowInsets(0, 0, 0, 0), title = { Text("Hi") } )
与 View 系统内边距的互操作性
如果屏幕同时包含 View 和 Compose 代码位于同一层次结构。在这种情况下,您需要在 哪个应该使用边衬区,哪个应该忽略边衬区。
例如,如果最外层布局是 Android View 布局,您应在 View 系统中使用内边距,并忽略 Compose 中的内边距。或者,如果最外层布局是可组合项,您应在 Compose 中使用内边距,并相应地为 AndroidView
可组合项添加内边距。
默认情况下,每个 ComposeView
均使用
能耗水平WindowInsetsCompat
。如需更改此默认行为,请将 ComposeView.consumeWindowInsets
设置为 false
。
资源
- Now in Android - 一款完全使用 Kotlin 和 Jetpack Compose 构建的功能齐全的 Android 应用。
- 应对 Android 15 强制执行的无边框措施 - 此 Codelab 将详细介绍 Android 15 强制执行的无边框措施
- 3 个可提升 Android 应用体验的功能:全屏显示、预测性返回和 Glance - 此 YouTube 视频介绍了 Android 15 全屏显示强制执行
- 无边框和边衬区 |撰写提示 - 介绍如何处理边衬区以绘制无边框的 YouTube 视频
为您推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- Material 组件和布局
- 将
CoordinatorLayout
迁移到 Compose - 其他注意事项