外观
BottomSheet — 底部弹层
从底部升上来的一层,插槽式:顶部 / 内容 / 底部三个区域。
顶部不预制任何东西。想要标题栏就把 BottomSheetHeader 传进 header 槽 —— 标题、副标题、右侧动作、关闭键、分割线都归它管。
┌──────────────────────────────┐
│ header (可空) │ ← 通常放 BottomSheetHeader
├──────────────────────────────┤ ← 它自带的分割线
│ content │ ← 内容槽:列表 / 表单 / 任意 composable
├──────────────────────────────┤
│ footer (可空) │ ← 底部固定区:主按钮、提示条…
└──────────────────────────────┘基本用法
标准形态(头部 + 内容):
kotlin
BottomSheet(
onDismissRequest = onDismiss,
header = {
BottomSheetHeader(
title = "选择分类",
onDismiss = onDismiss, // 关闭键的点击回调,自己接
subtitle = "按用途筛选",
actions = { // 「清空」这类次要动作,位于关闭键左侧
TertiaryButton(text = "清空", onClick = { clear() })
},
)
},
) {
Column(modifier = Modifier.padding(24.dp)) { // 表单内容:自己加内边距
// …
}
}列表内容(通栏,内边距交给每一行):
kotlin
BottomSheet(
onDismissRequest = onDismiss,
header = { BottomSheetHeader(title = "全部分类", onDismiss = onDismiss) },
) {
LazyColumn {
items(categories) { item ->
ListItem(title = item.name, onClick = { pick(item) })
}
}
}底部固定一个主按钮:
kotlin
BottomSheet(
onDismissRequest = onDismiss,
header = { BottomSheetHeader(title = "编辑资料", onDismiss = onDismiss) },
footer = {
FilledButton(
text = "保存",
onClick = { save() },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 16.dp),
)
},
) {
ProfileForm()
}不要头部:
kotlin
BottomSheet(onDismissRequest = onDismiss) {
// 只有内容 —— header 不传就没有顶部栏,也没有那条分割线
}参数
BottomSheet
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
onDismissRequest | () -> Unit | — | 关闭回调。点关闭键 / 点遮罩 / 下划 / 返回键都走它 |
modifier | Modifier | Modifier | 修饰符 |
skipPartiallyExpanded | Boolean | false | true 时只能「全展开」或「关闭」,没有半展开停靠点 |
colors | BottomSheetColors | BottomSheetDefaults.colors() | 配色。这里的 colors 只管容器底色 |
header | (@Composable () -> Unit)? | null | 顶部插槽。通常放 BottomSheetHeader;不传就没有顶部栏 |
footer | (@Composable () -> Unit)? | null | 底部固定槽。放主按钮、提示条这类不跟着内容滚的东西 |
content | @Composable ColumnScope.() -> Unit | — | 内容槽。放在最后,所以尾随 lambda 写法照常可用 |
BottomSheetHeader
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | String | — | 标题,单行 |
onDismiss | () -> Unit | — | 关闭回调。只挂在右上角关闭键上 |
modifier | Modifier | Modifier | 修饰符 |
subtitle | String? | null | 副标题,单行,空白等同于不传 |
showCloseButton | Boolean | true | 是否显示关闭键 |
actions | (@Composable () -> Unit)? | null | 右侧动作区,位于关闭键左侧 |
colors | BottomSheetColors | BottomSheetDefaults.colors() | 配色 |
标题栏自带 fillMaxWidth() 和内部边距(起止 24dp / 上 20dp / 下 16dp),末尾跟一条 HorizontalDivider()。
BottomSheetColors
4 个槽位,分属两个组件:
| 字段 | 默认值 | 归谁 |
|---|---|---|
containerColor | MaterialTheme.colorScheme.surfaceContainer | BottomSheet |
titleColor | MaterialTheme.colorScheme.onSurface | BottomSheetHeader |
subtitleColor | MaterialTheme.colorScheme.onSurfaceVariant | BottomSheetHeader |
closeIconTint | MaterialTheme.colorScheme.onSurfaceVariant | BottomSheetHeader |
要点
头部一律显式传。 这里没有
title/subtitle/showCloseButton/actions参数 —— 这四个只有BottomSheetHeader上有。旧代码还传着它们会编译报错,把它们挪进header里的BottomSheetHeader,并把onDismiss接上(一般就是同一个onDismissRequest)。没有
contentPadding,内边距自己管。 这个参数已经移除;旧代码传了会编译报错。理由:内容形态差别太大 —— 列表要通栏(内边距交给每一行,否则 24 + 16 变成双重缩进)、表单要 24dp、全宽图片要 0。给一个「万能 padding」只会让每种情况都得再覆盖一次。需要时在content里自己.padding(...),一眼能看出实际值。两部分
colors要分开传。BottomSheet的colors只管容器底色;标题文字和关闭键的颜色由传给BottomSheetHeader的那份决定。想一起改就传同一份:kotlinval sheetColors = BottomSheetDefaults.colors(containerColor = Color.Black) BottomSheet( colors = sheetColors, header = { BottomSheetHeader(title = "…", onDismiss = onDismiss, colors = sheetColors) }, ) { /* … */ }onDismiss只挂在关闭键上。 点遮罩 / 下划 / 返回键归BottomSheet的onDismissRequest管,两者通常传同一个 lambda。内容不多时开
skipPartiallyExpanded = true。 否则默认会有「半展开」这个停靠点,内容矮的时候会停在一个尴尬的高度。footer不跟着内容滚,它是弹层的固定底栏。想让按钮跟着内容走就放进content。标题栏的
actions在关闭键左侧,而且两者之间自带 8dp 间距,不用自己补。
说明
- 标题栏下方那条线固定用组件库的
HorizontalDivider()默认参数,不受colors控制,也没有dividerColor槽位。理由:分隔线本来就该比卡片描边更淡;HorizontalDivider的默认色是outlineVariant叠 70% 透明度、粗细 1 物理像素,全站统一这一个规格。这里要是再开一个可配的颜色槽位,同一个界面上迟早出现两种深浅的线。 BottomSheetColors和DialogColors是分开的两份。 底部弹窗是「从底下升上来的一层」,多一个右上角关闭键,着色点和居中弹窗并不一样 —— 合成一份会各自多出用不到的字段。- 形状是
AppShapes.sheetTop(只圆上边 28dp),和heroTop值相同但语义不同:一个说「底部弹层上沿」,一个说「折叠标题栏的内容卡片」。 dragHandle = null—— 关掉了 M3 自带的顶部拖拽条。有标题栏的时候那条横杠是多余的,头部本身就是可抓的地方。BottomSheetHeader可以单独用(比如在自己写的ModalBottomSheet里复用同一套头部),它不依赖BottomSheet。