Android 平台负责绘制系统界面,如 状态栏和导航栏。无论 用户使用的应用
WindowInsets
提供有关系统的信息
确保应用在正确的区域绘制且界面不会被遮挡的界面
由系统界面提供
![在系统栏后面全屏绘制](https://developer.android.google.cn/static/develop/ui/compose/images/layouts/insets/e2e-intro.gif?authuser=7&hl=fi)
在 Android 14(API 级别 34)及更低版本中,应用界面不会在其底层绘制 系统栏和刘海屏
在 Android 15(API 级别 35)及更高版本中,应用会在系统下绘制 当您的应用以 SDK 35 为目标平台后,通知栏和刘海屏。这样,您可以获得 让您的应用能够充分利用 可用的窗口空间
在系统界面后面显示内容的过程称为全屏显示。在本 页面,您将了解不同类型的边衬区、如何实现无边框 以及如何使用边衬区 API 为界面添加动画效果,并确保应用的内容 不会被系统界面元素遮挡
边衬区基础
当一款应用采用无边框格式时,您需要确保应用 互动不会被系统界面遮挡。例如,如果按钮是 放置在导航栏后面,用户可能无法点击。
指定系统界面的大小以及有关其放置位置的信息 通过边衬区实现。
系统界面的每个部分都有相应的边衬区类型, 以及放置位置例如,状态栏边衬区可提供 而导航栏边衬区则提供 导航栏的尺寸和位置每种类型的边衬区都由 像素尺寸:顶部、左侧、右侧和底部。这些维度指定了 系统界面从应用窗口的相应侧边扩展。为避免 与此类系统界面重叠,因此应用界面必须使用该类型的 金额。
以下内置 Android 边衬区类型可通过 WindowInsets
提供:
描述状态栏的边衬区。这些是包含通知图标和其他指示器的顶部系统界面栏。 |
|
状态栏用于设置何时可见。如果状态栏当前处于隐藏状态(由于进入沉浸式全屏模式),则主状态栏边衬区将为空,但这些边衬区将是非空。 |
|
描述导航栏的边衬区。这些是设备左侧、右侧或底部的系统界面栏,用于描述任务栏或导航图标。它们可能会在运行时发生变化,具体取决于用户的首选导航方法以及与任务栏的交互情况。 |
|
导航栏会带有边衬区(当它们处于可见状态时)。如果导航栏当前处于隐藏状态(由于进入沉浸式全屏模式),则主导航栏边衬区将为空,但这些边衬区将是非空。 |
|
描述系统界面窗口装饰(如果是在自由窗口)中的边衬区,例如顶部标题栏。 |
|
字幕栏会在其可见的情况下插入。如果字幕栏当前处于隐藏状态,则主字幕栏边衬区将为空,但这些边衬区将是非空的。 |
|
系统栏边衬区的集合,包括状态栏、导航栏和标题栏。 |
|
系统栏插入,用于指明何时可见。如果系统栏当前处于隐藏状态(由于进入沉浸式全屏模式),则主系统栏边衬区将为空,但这些边衬区将是非空。 |
|
描述软件键盘底部所占空间的边衬区。 |
|
描述当前键盘动画之前软件键盘所占空间的边衬区。 |
|
描述当前键盘动画播放完后软件键盘将占据的空间量的边衬区。 |
|
一种边衬区,用于描述有关导航界面的更多详细信息,并提供“点按”空间。将由系统(而非应用)进行处理。对于带有手势导航的透明导航栏,某些应用元素可通过系统导航界面点按。 |
|
可点按的元素在它们可见时插入。如果可点按元素当前处于隐藏状态(由于进入沉浸式全屏模式),则可点按元素的主要边衬区将为空,但这些边衬区将是非空的。 |
|
表示系统将在其中拦截导航手势的边衬区的边衬区。应用可以通过 |
|
系统手势的子集,将始终由系统处理,且无法通过 |
|
边衬区表示需要避免与刘海屏(凹口或针孔)重叠所需的间距量。 |
|
表示瀑布显示屏曲线区域的边衬区。瀑布式显示屏具有沿屏幕边缘的曲线区域,该区域开始沿着设备侧边的屏幕包围。 |
这些类型归纳为三个“安全”类型,这些边衬区类型可确保内容 遮挡:
这些“安全”的边衬区类型以不同的方式保护内容,具体取决于 底层平台边衬区:
- 使用
WindowInsets.safeDrawing
保护不应绘制的内容 任何系统界面下这是边衬区最常见的用法: 绘制被系统界面(部分或 )。 - 使用
WindowInsets.safeGestures
通过手势保护内容。这个 避免系统手势与应用手势(例如底部手势)冲突 表格、轮播界面或游戏中)。 - 将
WindowInsets.safeContent
作为以下组合使用WindowInsets.safeDrawing
和WindowInsets.safeGestures
,以确保 既没有视觉重叠,也没有手势重叠。
边衬区设置
如需允许应用完全控制其绘制内容的位置,请遵循以下设置 步骤。如果不执行这些步骤,您的应用可能会在 系统界面,或者不与软件键盘同步添加动画效果。
- 目标 SDK 35 或更高版本,以便在 Android 15 及更高版本上强制执行全屏。您的应用 显示在系统界面后面。您可以通过处理 边衬区。
- (可选)在以下位置调用
enableEdgeToEdge()
:Activity.onCreate()
,可让应用在上一个 Android 版本。 在 activity 的 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 关闭时,更改会反过来:Spacer
开始
一旦 imePadding()
应用的视图小于
直到 Spacer
与
在 IME 完全呈现动画后,显示在系统栏的底部。
TextField
的边缘延迟列。此行为是通过
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
或固定高度
分隔符:
@OptIn(ExperimentalLayoutApi::class) 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") } } }
![展示界面元素上下滚动以便为键盘腾出空间的动画](https://developer.android.google.cn/static/develop/ui/compose/images/interop-keyboard.gif?authuser=7&hl=fi)
对 Material 3 组件的边衬区支持
为了便于使用,许多内置的 Material 3 可组合项
(androidx.compose.material3
个)
根据可组合项在应用中的放置方式自行处理边衬区
根据 Material 规范
边衬区处理可组合项
下面列出了 Material Design 组件 自动处理边衬区
应用栏
TopAppBar
/SmallTopAppBar
/CenterAlignedTopAppBar
/MediumTopAppBar
/LargeTopAppBar
:应用系统栏的顶部和水平侧边作为内边距,因为它在窗口顶部使用。BottomAppBar
:应用系统栏的底部和水平侧边作为内边距。
内容容器
ModalDrawerSheet
/DismissibleDrawerSheet
/PermanentDrawerSheet
(模态抽屉式导航栏内的内容):对内容应用 vertical 和 start 边衬区。ModalBottomSheet
: 应用底部边衬区。NavigationBar
:应用底部和水平边衬区。NavigationRail
:应用 vertical 和 start 边衬区。
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 忽略它们。
或者,如果您最外层的布局是可组合项,那么您应该使用
边衬区,并相应地填充 AndroidView
可组合项。
默认情况下,每个 ComposeView
均使用
能耗水平WindowInsetsCompat
。要更改此默认行为,请将
ComposeView.consumeWindowInsets
发送至 false
。
资源
- Now in Android - 一款完全使用 Kotlin 和 Jetpack Compose 构建的功能齐全的 Android 应用。
- 在 Android 15 中处理全屏强制执行 - 一个 Codelab 介绍了 Android 15 全屏强制执行
- 提升 Android 应用体验的 3 大功能:无边框、预测性返回和一目了然 - 一段 YouTube 视频,讲解了 Android 15 全面屏政策的执行方式
- 无边框和边衬区 |撰写提示 - 介绍如何处理边衬区以绘制无边框的 YouTube 视频
为您推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- Material 组件和布局
- 将
CoordinatorLayout
迁移到 Compose - 其他注意事项