Jetpack Compose 提供了 Material Design 的实现,后者是一个用于创建数字化界面的综合设计系统。Material 组件(按钮、卡片、开关等)和布局(如 Scaffold
)可作为可组合函数提供。
Material 组件是用于创建界面的交互式构建块。Compose 提供了许多此类组件,开箱即可使用。如需了解提供了哪些组件,请参阅 Compose Material API 参考文档。
Material 组件会使用应用中 MaterialTheme
提供的值:
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
如需详细了解主题设置,请参阅 Compose 中的设计系统指南。
内容槽
支持内部内容(文本标签、图标等)的 Material 组件往往会提供“槽”(即接受可组合内容的通用 lambda),而且还会提供尺寸和内边距等公共常量,从而支持设置内部内容的布局,使之符合 Material 规范。
例如 Button
:
Button(
onClick = { /* ... */ },
// Uses ButtonDefaults.ContentPadding by default
contentPadding = PaddingValues(
start = 20.dp,
top = 12.dp,
end = 20.dp,
bottom = 12.dp
)
) {
// Inner content including an icon and a text label
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
图 1. 使用 content
槽和默认内边距的 Button
(左),以及使用提供自定义 contentPadding
的 content
槽的 Button
(右)。
Button
有一个通用 content
尾随 lambda 槽,该槽使用 RowScope
将内容可组合项的布局设为行。此外,它还有一个 contentPadding
参数,用于将内边距应用于内部内容。您可以使用通过 ButtonDefaults
提供的常量,也可以使用自定义值。
再比如 ExtendedFloatingActionButton
:
ExtendedFloatingActionButton(
onClick = { /* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = { Text("Like") }
)
图 2. 使用 icon
槽和 text
槽的 ExtendedFloatingActionButton
。
ExtendedFloatingActionButton
有两个槽,分别针对 icon
和 text
标签,而没有通用 content
lambda。虽然每个槽都支持通用的可组合内容,但该组件会自行判断这些内部内容的布局方式。它会在内部处理内边距、对齐方式和大小。
Scaffold
Compose 提供了便捷的布局,用于将 Material 组件组合成常见的屏幕图案。可组合项(例如 Scaffold
)提供了适用于各种组件和其他屏幕元素的槽。
屏幕内容
Scaffold
有一个通用 content
尾随 lambda 槽。lambda 会收到应该应用于内容根目录(例如,通过 Modifier.padding
)的 PaddingValues
实例,以便偏移顶部栏和底部栏(如果存在的话)。
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
应用栏
Scaffold
为顶部应用栏或底部应用栏提供了槽。系统将在内部处理可组合项的放置位置。
您可以使用 topBar
槽和 TopAppBar
:
Scaffold(
topBar = {
TopAppBar { /* Top app bar content */ }
}
) {
// Screen content
}
您可以使用 bottomBar
槽和 BottomAppBar
:
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
这些槽可用于 BottomNavigation
等其他 Material 组件。
此外,您还可以使用自定义可组合项 - 例如,查看 Owl 示例中的初始配置屏幕。
悬浮操作按钮
Scaffold
为悬浮操作按钮提供了槽。
您可以使用 floatingActionButton
槽和 FloatingActionButton
:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
}
) {
// Screen content
}
系统将在内部处理 FAB 可组合项的底部放置位置。您可以使用 floatingActionButtonPosition
参数来调整水平位置:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
如果您使用的是 Scaffold
可组合项的 bottomBar
槽,则可以使用 isFloatingActionButtonDocked
参数将 FAB 与底部应用栏重叠:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
图 3. 使用 floatingActionButton
槽和 bottomBar
槽的 Scaffold
。isFloatingActionButtonDocked
参数设为 false
(顶部)和 true
(底部)。
BottomAppBar
支持带有 cutoutShape
参数的 FAB 刘海屏,它接受任何 Shape
。最好提供停靠组件所使用的同一 Shape
。例如,FloatingActionButton
使用 MaterialTheme.shapes.small
,并将 50% 的边角大小作为其 shape
参数的默认值:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar(
// Defaults to null, that is, No cutout
cutoutShape = MaterialTheme.shapes.small.copy(
CornerSize(percent = 50)
)
) {
/* Bottom app bar content */
}
}
) {
// Screen content
}
图 4. 具有 BottomAppBar
和停靠 FloatingActionButton
的 Scaffold
。BottomAppBar
的自定义 cutoutShape
与 FloatingActionButton
所使用的 Shape
一致。
信息提示控件
Scaffold
提供了一种显示信息提示控件的方式。
这是通过 ScaffoldState
提供的,其中包含一个 SnackbarHostState
属性。您可以使用 rememberScaffoldState
创建一个 ScaffoldState
实例,并通过 scaffoldState
参数将其传递给 Scaffold
。SnackbarHostState
可提供对 showSnackbar
函数的访问权限。该挂起函数需要 CoroutineScope
(例如,使用 rememberCoroutineScope
),并可被调用以响应界面事件,从而在 Scaffold
中显示 Snackbar
。
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
scaffoldState.snackbarHostState
.showSnackbar("Snackbar")
}
}
)
}
) {
// Screen content
}
您可以提供可选操作,并调整 Snackbar
的时长。snackbarHostState.showSnackbar
函数可接受额外的 actionLabel
和 duration
参数,并返回 SnackbarResult
。
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
val result = scaffoldState.snackbarHostState
.showSnackbar(
message = "Snackbar",
actionLabel = "Action",
// Defaults to SnackbarDuration.Short
duration = SnackbarDuration.Indefinite
)
when (result) {
SnackbarResult.ActionPerformed -> {
/* Handle snackbar action performed */
}
SnackbarResult.Dismissed -> {
/* Handle snackbar dismissed */
}
}
}
}
)
}
) {
// Screen content
}
您可以使用 snackbarHost
参数提供自定义 Snackbar
。如需了解详情,请参阅 SnackbarHost API reference docs
。
抽屉式导航栏
Scaffold
为模态抽屉式导航栏提供了槽。系统将在内部处理可组合项的可拖动动作条和布局。
您可以使用 drawerContent
槽,该槽使用 ColumnScope
将抽屉式导航栏内容可组合项的布局设为列:
Scaffold(
drawerContent = {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
// Drawer items
}
) {
// Screen content
}
Scaffold
接受一些额外的抽屉式导航栏参数。例如,您可以使用 drawerGesturesEnabled
参数来切换抽屉式导航栏是否响应拖动:
Scaffold(
drawerContent = {
// Drawer content
},
// Defaults to true
drawerGesturesEnabled = false
) {
// Screen content
}
您可以通过 ScaffoldState
完成以编程方式打开和关闭抽屉式导航栏的操作,其中包含一个 DrawerState
属性,该属性应使用 scaffoldState
参数传递给 Scaffold
。DrawerState
可提供对 open
和 close
函数的访问权限,以及对与当前抽屉式导航栏状态相关的属性的访问权限。这些挂起函数需要 CoroutineScope
(例如,使用 rememberCoroutineScope
),并且可被调用以响应界面事件。
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
// Drawer content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Open or close drawer") },
onClick = {
scope.launch {
scaffoldState.drawerState.apply {
if (isClosed) open() else close()
}
}
}
)
}
) {
// Screen content
}
模态抽屉式导航栏
如果您想实现不含 Scaffold
的模态抽屉式导航栏,可以使用 ModalDrawer
可组合项。它接受与 Scaffold
类似的抽屉式导航栏参数。
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
如果您要实现底部抽屉式导航栏,可以使用 BottomDrawer
可组合项:
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
底部动作条
如果您要实现标准底部动作条,可以使用 BottomSheetScaffold
可组合项。它接受与 Scaffold
类似的参数,例如 topBar
、floatingActionButton
和 snackbarHost
。其中包含额外的参数,这些参数可提供底部动作条的显示方式。
您可以使用 sheetContent
槽,该槽使用 ColumnScope
将动作条内容可组合项的布局设为列:
BottomSheetScaffold(
sheetContent = {
// Sheet content
}
) {
// Screen content
}
BottomSheetScaffold
接受一些额外的动作条参数。例如,您可以使用 sheetPeekHeight
参数设置动作条的窥视高度。此外,您还可以使用 sheetGesturesEnabled
参数来切换抽屉式导航栏是否响应拖动。
BottomSheetScaffold(
sheetContent = {
// Sheet content
},
// Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
sheetPeekHeight = 128.dp,
// Defaults to true
sheetGesturesEnabled = false
) {
// Screen content
}
您可以通过 BottomSheetScaffoldState
完成以编程方式展开和收起动作条的操作,其中包含一个 BottomSheetState
属性。您可以使用 rememberBottomSheetScaffoldState
创建一个 BottomSheetScaffoldState
实例,并通过 scaffoldState
参数将其传递给 BottomSheetScaffold
。BottomSheetState
可提供对 expand
和 collapse
函数的访问权限,以及对与当前动作条状态相关的属性的访问权限。这些挂起函数需要 CoroutineScope
(例如,使用 rememberCoroutineScope
),并且可被调用以响应界面事件。
val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetContent = {
// Sheet content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Expand or collapse sheet") },
onClick = {
scope.launch {
scaffoldState.bottomSheetState.apply {
if (isCollapsed) expand() else collapse()
}
}
}
)
}
) {
// Screen content
}
如果您要实现模态底部动作条,可以使用 ModalBottomSheetLayout
可组合项:
val sheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
// Sheet content
}
) {
// Screen content
}
背景幕
如果您要实现背景幕,可以使用 BackdropScaffold
可组合项。
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)
BackdropScaffold
接受一些额外的背景幕参数。例如,您可以使用 peekHeight
和 headerHeight
参数来设置后层的窥视高度和前层的最小非活动高度。此外,您还可以使用 gesturesEnabled
参数来切换背景幕是否响应拖动。
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
},
// Defaults to BackdropScaffoldDefaults.PeekHeight
peekHeight = 40.dp,
// Defaults to BackdropScaffoldDefaults.HeaderHeight
headerHeight = 60.dp,
// Defaults to true
gesturesEnabled = false
)
您可以通过 BackdropScaffoldState
完成以编程方式显示和隐藏背景幕的操作。您可以使用 rememberBackdropScaffoldState
创建一个 BackdropScaffoldState
实例,并通过 scaffoldState
参数将其传递给 BackdropScaffold
。BackdropScaffoldState
可提供对 reveal
和 conceal
函数的访问权限,以及对与当前背景幕状态相关的属性的访问权限。这些挂起函数需要 CoroutineScope
(例如,使用 rememberCoroutineScope
),并且可被调用以响应界面事件。
val scaffoldState = rememberBackdropScaffoldState(
BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = {
TopAppBar(
title = { Text("Backdrop") },
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton(
onClick = {
scope.launch { scaffoldState.reveal() }
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu"
)
}
} else {
IconButton(
onClick = {
scope.launch { scaffoldState.conceal() }
}
) {
Icon(
Icons.Default.Close,
contentDescription = "Close"
)
}
}
},
elevation = 0.dp,
backgroundColor = Color.Transparent
)
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)