Skip to content

按钮 — 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 就是另外三个:

参数类型默认值说明
modifierModifierModifier修饰符
textString—按钮文字,单行、超长省略
onClick() -> Unit—点击回调
sizeButtonSizeButtonSize.MEDIUM尺寸档位。默认就是标准用法,别随手传
enabledBooleantrue是否启用
iconImageVector?null左侧图标
loadingBooleanfalse加载中:显示指示器,并强制禁用点击
containerColorColor?null一次性覆盖容器色;null 取 colors
contentColorColor?null一次性覆盖内容色;null 取 colors
colorsButtonColorsButtonDefaults.colors()配色

ActionImageButton ​

参数类型默认值说明
iconImageVector—图标
onAction() -> Unit—点击回调
modifierModifierModifier修饰符
iconTintColorMaterialTheme.colorScheme.onSurface图标颜色
contentDescriptionString?null无障碍描述

触控区 44dp、内部图标 24dp。

ButtonSize ​

档位高度文字图标加载指示器图标 / 文字间距内边距(水平 / 垂直)
SMALL32dplabelSmall16dp14dp6dp14 / 4
MEDIUM40dplabelLarge18dp16dp8dp20 / 8
LARGE48dplabelLarge20dp18dp10dp24 / 10

ButtonColors ​

字段默认值
filledContainerColorMaterialTheme.colorScheme.primary
filledContentColorMaterialTheme.colorScheme.onPrimary
tonalContainerColorMaterialTheme.colorScheme.secondaryContainer
tonalContentColorMaterialTheme.colorScheme.onSecondaryContainer
outlinedContentColorMaterialTheme.colorScheme.primary
outlinedBorderColorMaterialTheme.colorScheme.primary
tertiaryContentColorMaterialTheme.colorScheme.onSurfaceVariant
disabledContainerColorMaterialTheme.colorScheme.onSurface @ 12%
disabledContentColorMaterialTheme.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 一致 —— 属于冗余而非偏离,不影响观感。