外观
按钮 — FilledButton / TonalButton / OutlinedButton / TertiaryButton
四个形态各是一个组件,按视觉权重从高到低。形态由你用哪个组件表达,参数里不再有 type。另外还有一个纯图标的 ActionImageButton。
基本用法
kotlin
FilledButton(text = "保存", onClick = { save() }) // 主操作
TonalButton(text = "预览", onClick = { preview() }) // 次级
OutlinedButton(text = "取消", onClick = { dismiss() }) // 次次级
TertiaryButton(text = "跳过", onClick = { skip() }) // 弱动作带图标 / 加载中 / 禁用:
kotlin
FilledButton(text = "新建", onClick = { create() }, icon = Phosphor.Plus)
FilledButton(text = "提交中", onClick = { submit() }, loading = true)
FilledButton(text = "提交", onClick = { submit() }, enabled = false)三种尺寸:
kotlin
FilledButton(text = "小", onClick = { }, size = ButtonSize.SMALL) // 32dp
FilledButton(text = "中", onClick = { }, size = ButtonSize.MEDIUM) // 40dp(默认)
FilledButton(text = "大", onClick = { }, size = ButtonSize.LARGE) // 48dp整套换色 / 单个覆盖:
kotlin
FilledButton(
text = "删除",
onClick = { delete() },
colors = ButtonDefaults.colors(
filledContainerColor = MaterialTheme.colorScheme.error,
filledContentColor = MaterialTheme.colorScheme.onError,
),
)
FilledButton(text = "保存", onClick = { save() }, containerColor = Color.Black)标题栏里的纯图标按钮:
kotlin
TopTitleScaffold(
title = "设置",
onBack = { finish() },
actions = {
ActionImageButton(
icon = Phosphor.Gear,
onAction = { openSettings() },
contentDescription = "设置",
)
},
) { /* content */ }参数
四个按钮参数表完全一致,只有内部形态色不同。把名字换成 TonalButton / OutlinedButton / TertiaryButton 就是另外三个:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modifier | Modifier | Modifier | 修饰符 |
text | String | — | 按钮文字,单行、超长省略 |
onClick | () -> Unit | — | 点击回调 |
size | ButtonSize | ButtonSize.MEDIUM | 尺寸档位。默认就是标准用法,别随手传 |
enabled | Boolean | true | 是否启用 |
icon | ImageVector? | null | 左侧图标 |
loading | Boolean | false | 加载中:显示指示器,并强制禁用点击 |
containerColor | Color? | null | 一次性覆盖容器色;null 取 colors |
contentColor | Color? | null | 一次性覆盖内容色;null 取 colors |
colors | ButtonColors | ButtonDefaults.colors() | 配色 |
ActionImageButton
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
icon | ImageVector | — | 图标 |
onAction | () -> Unit | — | 点击回调 |
modifier | Modifier | Modifier | 修饰符 |
iconTint | Color | MaterialTheme.colorScheme.onSurface | 图标颜色 |
contentDescription | String? | null | 无障碍描述 |
触控区 44dp、内部图标 24dp。
ButtonSize
| 档位 | 高度 | 文字 | 图标 | 加载指示器 | 图标 / 文字间距 | 内边距(水平 / 垂直) |
|---|---|---|---|---|---|---|
SMALL | 32dp | labelSmall | 16dp | 14dp | 6dp | 14 / 4 |
MEDIUM | 40dp | labelLarge | 18dp | 16dp | 8dp | 20 / 8 |
LARGE | 48dp | labelLarge | 20dp | 18dp | 10dp | 24 / 10 |
ButtonColors
| 字段 | 默认值 |
|---|---|
filledContainerColor | MaterialTheme.colorScheme.primary |
filledContentColor | MaterialTheme.colorScheme.onPrimary |
tonalContainerColor | MaterialTheme.colorScheme.secondaryContainer |
tonalContentColor | MaterialTheme.colorScheme.onSecondaryContainer |
outlinedContentColor | MaterialTheme.colorScheme.primary |
outlinedBorderColor | MaterialTheme.colorScheme.primary |
tertiaryContentColor | MaterialTheme.colorScheme.onSurfaceVariant |
disabledContainerColor | MaterialTheme.colorScheme.onSurface @ 12% |
disabledContentColor | MaterialTheme.colorScheme.onSurface @ 38% |
要点
- 一屏只该有一个
FilledButton。 它是视觉权重最高的元素,同屏出现两个实心主色块会互相抢注意力 —— 这和PillTabsRow选中态警告的是同一件事。其余动作按重要性用TonalButton→OutlinedButton→TertiaryButton往下排。 loading = true会连带把点击一起禁掉(内部条件是enabled && !loading),不用自己再传enabled = false。loading优先于icon。 加载中时图标不显示,换成同色指示器。两者都要得自己拼。containerColor在OutlinedButton/TertiaryButton上不生效。 这两种形态走的是 M3 的outlinedButtonColors/textButtonColors,没有容器色入口 —— 它们的底恒定是透明的。想给底色,自己在外面包Modifier.background()。FilledButton/TonalButton上它正常工作。containerColor/contentColor是「一次性覆盖」,colors是「整套换」。 只改一个按钮用前者;改一整页所有按钮用后者。OutlinedButton禁用时描边会跟着变灰(换成onSurface@12%)。这是刻意的,理由见「说明」。ActionImageButton的contentDescription在纯图标场景必须给,否则读屏用户不知道这个按钮是干什么的。装饰性图标才传null。- 想要「带文字 + 图标」的按钮用上面四个,不要拿
ActionImageButton硬凑。 - 形状固定是胶囊,不可配。 换成方角会破坏全站按钮的形态一致性。
说明
- 四个名字共用一个私有实现。 形态之间只差三件事 —— 用哪个 M3 容器、默认容器色 / 内容色取哪一项、要不要描边。尺寸计算、禁用态处理、
loading/icon的内容布局完全共用;把同一段逻辑抄四份,迟早会有一份漏改。 - 禁用态用 M3 规范的中性混色(容器
onSurface@12%、文字onSurface@38%),不是「主色降透明度」。上一版禁用态是primary@38%底 +onPrimary@60%字,白字压在浅粉上实测只有 1.85:1 —— 看起来不是「被禁用」,而是「按钮坏了 / 颜色错了」。WCAG 1.4.3 明确豁免 inactive 组件的对比度要求,所以这里灰是刻意的。 - 禁用态不随形态变,也不进形态各自的槽位。 「灰掉」这件事在任何按钮上都必须长得一样,否则用户分不清是禁用还是新样式。
OutlinedButton的描边取primary而不是 M3 默认的outline:它是「次级动作」而不是装饰,主线框让它仍然被认成一个可操作控件。但禁用时必须换成中性值(见「要点」),否则会出现「边框还是鲜亮的主色、文字已经灰掉」的矛盾状态,用户会以为按钮还能点。- 四个形态各有一组独立的容器 / 内容槽位。 形态之间本来就是不同色,合成一组的话,调用方想改
OUTLINED的文字色就会把FILLED的也改掉。 LARGE就停在 48dp,那是 M3 的最小触控尺寸;再往上加只会显得笨重。- 文字字重被硬编码成
FontWeight.Medium,与labelSmall/labelLarge自身的 Medium 一致 —— 属于冗余而非偏离,不影响观感。