Button:从静态圆角到按压形变 · AndroidX 源码指南
AAndroidX 源码指南
M3E 设计系统
ANDROIDX / COMPOSE / MATERIAL 3

Button:从静态圆角到按压形变

理解 Button 的普通重载与 ButtonShapes 重载,以及 shapes() 和 shapesFor(height) 的区别。

为什么主题开了,按钮还没变形

Button 同时保留了普通 shape: Shape 重载和新的 shapes: ButtonShapes 重载。只有后者持有“常态形状 + 按压形状”,才能在交互状态之间变化。

官方 sample 的最小用法就是:

Button(
    onClick = {},
    shapes = ButtonDefaults.shapes(),
) {
    Text("Button")
}

这也是为什么不能只靠 MaterialExpressiveTheme 自动给所有按钮增加形变:是否使用新重载,是组件调用点自己选择的。

shapes()shapesFor(height)

shapes()

直接返回当前主题缓存的默认 ButtonShapes。源码使用 small button token 的常态与按压形状。

shapesFor(height)

先按给定高度归入 extra small / small / medium / large / extra large 档,再选择相应 pressedShape。

因此,标准小按钮可以用 shapes();当你明确使用不同容器高度时,shapesFor(buttonHeight) 能让按压形状与尺寸档位匹配。

val height = ButtonDefaults.LargeContainerHeight

Button(
    onClick = {},
    modifier = Modifier.heightIn(min = height),
    shapes = ButtonDefaults.shapesFor(height),
) {
    Text("Large action")
}

自定义两种状态

Button(
    onClick = {},
    shapes = ButtonDefaults.shapes(
        shape = RoundedCornerShape(24.dp),
        pressedShape = RoundedCornerShape(8.dp),
    ),
) {
    Text("Morph")
}

ButtonShapes 的两个 shape 都是 CornerBasedShape 时,形状才能按实现预期平滑插值。自定义前先保证交互反馈没有损害文字空间和可点击区域。

哪些按钮支持

当前 public API 中,ButtonElevatedButtonFilledTonalButtonOutlinedButtonTextButton 都有接收 ButtonShapes 的重载;官方 samples 也分别提供了 animated shape 示例。

复制即用

按下变形的按钮,复制即用:

import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun MorphButton() {
    // 最小用法:形状随按压状态变化
    Button(
        onClick = {},
        shapes = ButtonDefaults.shapes(),
    ) {
        Text("Button")
    }
}

@Composable
fun LargeMorphButton() {
    // 大按钮:按压形状与尺寸档位匹配
    val height = ButtonDefaults.LargeContainerHeight

    Button(
        onClick = {},
        modifier = Modifier.heightIn(min = height),
        shapes = ButtonDefaults.shapesFor(height),
    ) {
        Text("Large action")
    }
}

@Composable
fun CustomMorphButton() {
    // 自定义常态与按压形状
    Button(
        onClick = {},
        shapes = ButtonDefaults.shapes(
            shape = RoundedCornerShape(24.dp),
            pressedShape = RoundedCornerShape(8.dp),
        ),
    ) {
        Text("Morph")
    }
}

ElevatedButtonFilledTonalButtonOutlinedButtonTextButton 都有接收 ButtonShapes 的相同重载,替换类名即可。