如何在Flutter中实现FAB转换为material.io风格的动作菜单?
实现Material Design风格的FAB转动作菜单效果
嘿,我刚好之前折腾过这个需求!Material.io上那个FAB展开成动作菜单的效果确实很实用,官方Flutter API里确实没有直接的组件,但咱们有两种靠谱的实现方式:
一、用第三方包快速实现
最省心的方式是用成熟的第三方包,比如flutter_speed_dial,这个包完全贴合Material Design规范,支持各种动画效果、自定义样式,上手超快。
基本步骤:
- 在
pubspec.yaml里添加依赖:
dependencies: flutter_speed_dial: ^latest_version
- 简单示例代码:
SpeedDial( // 主FAB的样式 icon: Icons.add, activeIcon: Icons.close, backgroundColor: Colors.blue, // 展开后的动作按钮列表 children: [ SpeedDialChild( child: Icons.camera, label: '拍照', onTap: () => print('点击拍照'), ), SpeedDialChild( child: Icons.note_add, label: '新建笔记', onTap: () => print('新建笔记'), ), // 可以添加更多按钮 ], // 可选:设置动画类型,比如从FAB弹出的效果 animationCurve: Curves.easeInOut, animationDuration: const Duration(milliseconds: 300), )
这个包还支持自定义label样式、按钮排列方向(垂直/水平),甚至可以添加背景遮罩,完全能满足Material Design的效果要求。
二、自定义实现(适合追求完全可控的场景)
如果你不想依赖第三方包,自己写也不难,核心是用Flutter的动画组件来控制动作按钮的展开/收起状态。
核心思路:
- 用
StatefulWidget维护一个isExpanded状态,标记菜单是否展开 - 用
AnimatedContainer、AnimatedPositioned或者SlideTransition来实现动作按钮的动画 - 主FAB点击时切换状态,触发动画
简单自定义示例:
class FabActionMenu extends StatefulWidget { const FabActionMenu({super.key}); @override State<FabActionMenu> createState() => _FabActionMenuState(); } class _FabActionMenuState extends State<FabActionMenu> with SingleTickerProviderStateMixin { bool _isExpanded = false; late AnimationController _animationController; late Animation<double> _opacityAnimation; late Animation<double> _translateAnimation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _opacityAnimation = Tween<double>(begin: 0, end: 1).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); _translateAnimation = Tween<double>(begin: 40, end: 0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _toggleMenu() { setState(() { _isExpanded = !_isExpanded; if (_isExpanded) { _animationController.forward(); } else { _animationController.reverse(); } }); } @override Widget build(BuildContext context) { return Stack( alignment: Alignment.bottomRight, children: [ // 展开的动作按钮 if (_isExpanded) FadeTransition( opacity: _opacityAnimation, child: SlideTransition( position: Tween<Offset>( begin: const Offset(0, 1), end: Offset.zero, ).animate(_animationController), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ _buildActionButton(Icons.camera, '拍照', () => print('拍照')), const SizedBox(height: 16), _buildActionButton(Icons.note_add, '新建笔记', () => print('新建笔记')), const SizedBox(height: 16), ], ), ), ), // 主FAB FloatingActionButton( onPressed: _toggleMenu, child: Icon(_isExpanded ? Icons.close : Icons.add), ), ], ); } Widget _buildActionButton(IconData icon, String label, VoidCallback onTap) { return Row( mainAxisSize: MainAxisSize.min, children: [ if (_isExpanded) FadeTransition( opacity: _opacityAnimation, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.only(right: 16), decoration: BoxDecoration( color: Colors.grey.shade800, borderRadius: BorderRadius.circular(8), ), child: Text( label, style: const TextStyle(color: Colors.white), ), ), ), FloatingActionButton.small( onPressed: onTap, child: Icon(icon), ), ], ); } }
这个自定义实现里,我们用了FadeTransition和SlideTransition来实现动作按钮的淡入和滑入效果,完全符合Material Design的动效要求,你还可以根据自己的需求调整动画曲线、按钮样式、排列方式等。
两种方式各有优劣:第三方包省时间,自定义实现更灵活,你可以根据项目需求选择~
内容的提问来源于stack exchange,提问作者odonckers
相关产品推荐
相关产品推荐

