如何在Material3中通过Jetpack Compose实现FAB转菜单效果?
在Jetpack Compose Material3中实现FAB转菜单的动效
我想在Material3(M3)里用Jetpack Compose实现浮动操作按钮(FAB)转换为菜单的动效,试过官方的FloatingActionMenu但效果不符合需求。现有基础缩放动画代码如下:
// Scale Animation val scale by animateFloatAsState( targetValue = if (isPressed) 0.9f else 1f, animationSpec = tween(150, easing = FastOutSlowInEasing) ) FloatingActionButton( modifier = Modifier.scale(scale), onClick = { /* Handle click */ } ) { Icon(Icons.Default.Add, null) }
目标效果参考图:
参考M3动效规范中的「FAB转菜单」转场模式。
实现思路
通过状态控制主FAB的旋转、缩放动画,同时联动子菜单按钮的偏移、透明度动画,还原M3规范里的转场效果。核心是利用Compose的动画API(animateFloatAsState、animateOffsetAsState)实现状态驱动的动效同步。
完整实现代码
import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp @Composable fun FabToMenuTransition() { var isExpanded by remember { mutableStateOf(false) } // 主FAB旋转动画:Add图标转为关闭样式 val rotation by animateFloatAsState( targetValue = if (isExpanded) 45f else 0f, animationSpec = tween(300, easing = FastOutSlowInEasing) ) // 主FAB缩放动画 val fabScale by animateFloatAsState( targetValue = if (isExpanded) 0.85f else 1f, animationSpec = tween(250, easing = FastOutSlowInEasing) ) Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) { // 子菜单:编辑按钮 val editOffset by animateOffsetAsState( targetValue = if (isExpanded) Offset(x = 0f, y = -80.dp.toPx()) else Offset.Zero, animationSpec = tween(300, easing = FastOutSlowInEasing) ) if (isExpanded || editOffset.y != 0f) { FloatingActionButton( modifier = Modifier .offset { editOffset } .graphicsLayer { alpha = if (isExpanded) 1f else 0f }, onClick = { /* 处理编辑操作 */ }, containerColor = MaterialTheme.colorScheme.secondaryContainer ) { Icon(Icons.Default.Edit, contentDescription = "编辑") } } // 子菜单:删除按钮 val deleteOffset by animateOffsetAsState( targetValue = if (isExpanded) Offset(x = 0f, y = -160.dp.toPx()) else Offset.Zero, animationSpec = tween(350, easing = FastOutSlowInEasing) ) if (isExpanded || deleteOffset.y != 0f) { FloatingActionButton( modifier = Modifier .offset { deleteOffset } .graphicsLayer { alpha = if (isExpanded) 1f else 0f }, onClick = { /* 处理删除操作 */ }, containerColor = MaterialTheme.colorScheme.errorContainer ) { Icon(Icons.Default.Delete, contentDescription = "删除") } } // 主FAB FloatingActionButton( modifier = Modifier .padding(16.dp) .graphicsLayer { scaleX = fabScale scaleY = fabScale rotationZ = rotation }, onClick = { isExpanded = !isExpanded } ) { Icon(Icons.Default.Add, contentDescription = if (isExpanded) "关闭菜单" else "打开菜单") } } }
关键细节说明
- 主FAB动效:通过
rotationZ实现Add图标旋转45度模拟关闭符号,配合缩放动画强化交互反馈 - 子菜单动效:用
animateOffsetAsState让子按钮从主FAB位置向上滑动展开,同时控制透明度实现淡入淡出 - 状态联动:通过
isExpanded状态统一触发所有动画,保证动效同步流畅 - 动效曲线:使用M3推荐的
FastOutSlowInEasing,确保动画符合Material设计的自然流畅感
内容的提问来源于stack exchange,提问作者Muhammad Awais
相关产品推荐
相关产品推荐

