外观
AlertDialog — 弹窗
三槽插槽式:标题、内容、底部三个区域都由调用方决定放什么。组件本身只负责外观与关闭行为,不做任何按钮数量、文案、排布的约定。
旧的便捷式(SimpleAlertDialog,以及 title: String / text: String / confirmText 那一套重载)已删除。标题由调用方自己传 DialogTitle。
┌──────────────────────────┐
│ title (可空) │ ← 常放 DialogTitle;也可放自定义头部
│ content │ ← 正文:文本 / 输入框 / 列表 / 滚动容器…
│ bottom (可空) │ ← 底部动作区:横排、竖排堆叠都行
└──────────────────────────┘基本用法
两个按钮(最常见的确认弹窗):
kotlin
AlertDialog(
onDismissRequest = onDismiss,
title = { DialogTitle("退出登录") },
content = { Text("退出后,收藏与最近使用记录仍保留在本机。") },
bottom = {
DialogButtonRow {
TertiaryButton(text = "取消", onClick = onDismiss)
FilledButton(text = "退出", onClick = onConfirm)
}
},
)三个以上按钮直接竖排(不要硬塞进 DialogButtonRow):
kotlin
AlertDialog(
onDismissRequest = onDismiss,
title = { DialogTitle("有新版本", supportingText = "本次更新修复了若干问题") },
content = { Text("建议现在更新,也可以稍后在设置里手动检查。") },
bottom = {
FilledButton(text = "立即更新", onClick = onUpdate, modifier = Modifier.fillMaxWidth())
TonalButton(text = "稍后提醒", onClick = onLater, modifier = Modifier.fillMaxWidth())
TertiaryButton(text = "跳过此版本", onClick = onSkip, modifier = Modifier.fillMaxWidth())
},
)没有标题、内容要滚动:
kotlin
AlertDialog(
onDismissRequest = onDismiss,
content = {
Column(
modifier = Modifier
.heightIn(max = 360.dp)
.verticalScroll(rememberScrollState()),
) {
Text(agreementText)
}
},
bottom = { DialogButtonRow { FilledButton(text = "同意", onClick = onAgree) } },
)强制类弹窗(不允许返回键 / 点击外部关闭):
kotlin
AlertDialog(
onDismissRequest = { },
dismissOnBackPress = false,
dismissOnClickOutside = false,
title = { DialogTitle("强制更新") },
content = { Text("不更新将无法继续使用。") },
bottom = { FilledButton(text = "立即更新", onClick = onUpdate, modifier = Modifier.fillMaxWidth()) },
)参数
AlertDialog
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
onDismissRequest | () -> Unit | — | 请求关闭(返回键 / 点击外部) |
modifier | Modifier | Modifier | 外层修饰符 |
dismissOnBackPress | Boolean | true | 返回键是否可关闭(强制类弹窗传 false) |
dismissOnClickOutside | Boolean | true | 点击弹窗外部是否可关闭 |
contentPadding | PaddingValues | 起止 24dp / 上 8dp / 下 20dp | 内容区内边距 |
bottomPadding | PaddingValues | 起止 24dp / 上 8dp / 下 24dp | 底部区内边距 |
colors | DialogColors | DialogDefaults.colors() | 配色 |
title | (@Composable ColumnScope.() -> Unit)? | null | 标题槽(可空) |
content | @Composable ColumnScope.() -> Unit | {} | 内容槽 |
bottom | (@Composable ColumnScope.() -> Unit)? | null | 底部槽(可空) |
三个槽都是 ColumnScope。 横排竖排由调用方自己决定。
三个槽放什么
| 槽 | 典型内容 |
|---|---|
title | DialogTitle(text = "标题");不需要标题栏就不传(标题区连内边距一起消失) |
content | 一段 Text、一个 TextField、一个可滚动的 Column…… |
bottom | 「取消 + 确认」两个按钮横排;按钮多时依次排开就是竖排 |
DialogTitle
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
text | String | — | 标题,用 headlineSmall |
supportingText | String? | null | 标题下方补充说明(bodySmall),空白等同于不传 |
modifier | Modifier | Modifier | 修饰符 |
colors | DialogColors | DialogDefaults.colors() | 配色 |
标题与说明之间是 6dp。
DialogButtonRow
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modifier | Modifier | Modifier | 修饰符。内部会 fillMaxWidth() |
content | @Composable RowScope.() -> Unit | — | 按钮。内部是 RowScope |
排布是 Arrangement.spacedBy(8.dp, Alignment.End) —— 右对齐、间距 8dp。
DialogColors
| 字段 | 默认值 |
|---|---|
containerColor | MaterialTheme.colorScheme.surfaceContainerHigh |
titleColor | MaterialTheme.colorScheme.onSurface |
supportingTextColor | MaterialTheme.colorScheme.onSurfaceVariant |
contentColor | MaterialTheme.colorScheme.onSurfaceVariant |
没有按钮色 —— 按钮自己取色(FilledButton / TertiaryButton 那套层级体系),弹窗再给一份会和它打架。
要点
Modifier.weight需要RowScope。 想给按钮分配宽度,必须包在DialogButtonRow里;直接写在bottom里编译不过(那是ColumnScope)。- 不要标题就别传
title。 不传时标题区连同它那 24 / 12 的内边距一起消失,不会留一块空洞。 - 三个以上按钮不要用
DialogButtonRow。 在bottom里依次排开即可 ——ColumnScope天然竖排,每个按钮Modifier.fillMaxWidth()占满一行,点击区更大、层级也更清楚。 onDismissRequest是「返回键 / 点击外部」的回调,和bottom里按钮的onClick不是一回事。按钮点「取消」时通常自己调同一个 lambda。content有默认值{},所以可以只写标题和底部;但onDismissRequest必传。- 内容很长时自己给高度上限 + 滚动(
heightIn(max = …)+verticalScroll),弹窗本身不滚。 - 单个按钮也可以放在
bottom里,ColumnScope会把它按左对齐的整宽块排;想靠右就用DialogButtonRow。
说明
- 容器色是
surfaceContainerHigh,形状是MaterialTheme.shapes.extraLarge(32dp)。 M3 里对话框是「浮在内容之上的高海拔表面」,用更高的表面音阶而不是 16dp 投影来表达层级 —— 深色主题下纯surface的对话框会和背景糊在一起,看不出是弹层。所以这里tonalElevation和shadowElevation都显式设成 0。 - 宽度是
fillMaxWidth(0.86f)+widthIn(max = 420.dp):手机上左右各留 7% 边距,平板上不会拉成一整条。它用的是usePlatformDefaultWidth = false—— 平台默认宽度太窄,这里自己接管。 DialogTitle是「标准标题」,不是必须的。title槽是自由的 —— 想放自定义头部(比如带图标的标题行)就直接放,不受DialogTitle限制。DialogButtonRow只是语法糖,存在的唯一理由是省掉「每个两按钮弹窗都手写一遍右对齐Row」。- 头部 / 内容 / 底部是三段独立的内边距,不是一个大
padding包住:头部 24/12、内容区按contentPadding、底部按bottomPadding。这样头部挨着内容、底部动作区自己留白,中间不会出现双份间距。