M3E 设计系统
ANDROIDX / COMPOSE / MATERIAL 3
FloatingActionButton:突出动作与滚动响应
选择 FAB 尺寸、实现 Extended 展开收起,并用 MotionScheme 驱动显示隐藏。
FAB 不是普通圆形按钮
AndroidX KDoc 对 FAB 的定位非常明确:它代表屏幕上最重要的动作。一个页面放多个互相竞争的 FAB,会破坏这层信息优先级。
四种尺寸入口
SmallFloatingActionButtonFloatingActionButtonMediumFloatingActionButtonLargeFloatingActionButton
它们不仅是同一个按钮随意 size()。源码分别从 small、default、medium、large token 读取最小容器尺寸和默认 shape,图标尺寸也有对应的 Defaults 属性。
MediumFloatingActionButton(onClick = { /* create */ }) {
Icon(
Icons.Filled.Add,
contentDescription = "新建",
modifier = Modifier.size(FloatingActionButtonDefaults.MediumIconSize),
)
}随滚动显示和隐藏
源码提供了专用 modifier:
val visible by remember {
derivedStateOf { listState.firstVisibleItemIndex == 0 }
}
MediumFloatingActionButton(
modifier = Modifier.animateFloatingActionButton(
visible = visible,
alignment = Alignment.BottomEnd,
),
onClick = {},
) {
Icon(Icons.Filled.Add, contentDescription = "新建")
}如果没有自定义 animation spec,实现默认使用:
- 缩放:MotionScheme 的 Fast Spatial
- 透明度:MotionScheme 的 Fast Effects
这正是主题级 MotionScheme 能发挥作用的地方:调用方只表达 visible,组件负责选择统一的运动规格。
Extended FAB 是展开,不只是加文字
ExtendedFloatingActionButton 有接收 expanded 的重载。AndroidX sample 根据列表滚动位置切换 expanded,在紧凑状态只保留图标:
ExtendedFloatingActionButton(
expanded = expandedFab,
onClick = {},
icon = { Icon(Icons.Filled.Add, contentDescription = "新建") },
text = { Text("新建项目") },
)折叠后文字不可见,因此图标的 contentDescription 和 Tooltip 不能省略。官方 FAB 动画 sample 也明确包裹了 TooltipBox。
迁移建议
如果已有页面使用普通 FloatingActionButton:
- 先确认它确实是页面唯一的主要动作。
- 根据视觉层级选择尺寸,而不是直接放大 Modifier。
- 滚动页面再考虑
animateFloatingActionButton。 - 只有文字能显著降低歧义时才采用 Extended。
复制即用
随滚动显示隐藏的 FAB:
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.MediumFloatingActionButton
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun ScrollAwareFab() {
val listState = rememberLazyListState()
val visible by remember {
derivedStateOf { listState.firstVisibleItemIndex == 0 }
}
LazyColumn(state = listState) {
items(50) { index -> /* item */ }
}
MediumFloatingActionButton(
modifier = Modifier.animateFloatingActionButton(
visible = visible,
alignment = Alignment.BottomEnd,
),
onClick = {},
) {
Icon(Icons.Filled.Add, contentDescription = "新建")
}
}动画默认使用主题 MotionScheme 的 Fast Spatial/Fast Effects;不要在一个页面放多个 FAB。