如何在Flutter中实现类似WhatsApp的动画弹出菜单效果
实现WhatsApp风格消息气泡展开/折叠动画
核心实现思路
- 用
AnimatedContainer处理气泡的尺寸平滑过渡,这是Flutter中实现容器类动画最便捷的组件 - 结合
AnimatedOpacity控制联系人名称的显示/隐藏透明度 - 通过
AnimationController配合Transform.scale、Transform.translate实现图标的缩放和位置偏移 - 用
GestureDetector绑定点击事件,切换展开/折叠状态
完整可运行代码示例
import 'package:flutter/material.dart'; class WhatsAppMessageBubble extends StatefulWidget { const WhatsAppMessageBubble({super.key}); @override State<WhatsAppMessageBubble> createState() => _WhatsAppMessageBubbleState(); } class _WhatsAppMessageBubbleState extends State<WhatsAppMessageBubble> with SingleTickerProviderStateMixin { bool _isExpanded = false; late AnimationController _animController; late Animation<double> _iconScaleAnim; late Animation<double> _nameSlideAnim; @override void initState() { super.initState(); _animController = AnimationController( duration: const Duration(milliseconds: 250), vsync: this, ); // 图标缩放动画:展开时缩小到0.8倍 _iconScaleAnim = Tween<double>(begin: 1.0, end: 0.8).animate( CurvedAnimation(parent: _animController, curve: Curves.easeInOut), ); // 名称滑动动画:从左侧偏移位置滑入 _nameSlideAnim = Tween<double>(begin: -20, end: 0).animate( CurvedAnimation(parent: _animController, curve: Curves.easeInOut), ); } @override void dispose() { _animController.dispose(); super.dispose(); } void _toggleExpandState() { setState(() => _isExpanded = !_isExpanded); _isExpanded ? _animController.forward() : _animController.reverse(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _toggleExpandState, child: Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: AnimatedContainer( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, width: _isExpanded ? double.infinity : 60, height: 60, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(30), ), padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ Transform.scale( scale: _iconScaleAnim.value, child: const CircleAvatar( backgroundImage: NetworkImage('https://picsum.photos/200'), radius: 20, ), ), if (_isExpanded) Transform.translate( offset: Offset(_nameSlideAnim.value, 0), child: AnimatedOpacity( opacity: _isExpanded ? 1.0 : 0.0, duration: const Duration(milliseconds: 200), child: const Padding( padding: EdgeInsets.only(left: 12), child: Text( '联系人名称', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), ), ), ), const Spacer(), if (_isExpanded) const Icon(Icons.keyboard_voice_outlined), ], ), ), ), ); } } // 调用示例 void main() { runApp(const MaterialApp( home: Scaffold( backgroundColor: Colors.white, body: Center(child: WhatsAppMessageBubble()), ), )); }
关键细节说明
- 气泡尺寸过渡:
AnimatedContainer自动处理宽度从固定值到全屏的平滑变化,Curves.easeInOut保证动画自然不生硬 - 图标缩放:通过动画控制器驱动缩放值,展开时图标缩小,贴合WhatsApp的视觉逻辑
- 名称入场动画:组合
Transform.translate和AnimatedOpacity,让名称从左侧滑入同时渐显,避免突兀出现 - 状态同步:点击切换状态时,同步控制动画控制器的正向/反向播放,保证动画与状态一致
扩展优化方向
- 可添加消息预览文本的展开动画,丰富气泡内容
- 自定义
Curves参数,调整动画速率,更贴近原版效果 - 在列表中使用时,可配合
AutomaticKeepAliveClientMixin保持组件状态,避免滚动时重置
内容的提问来源于stack exchange,提问作者Hudaif
相关产品推荐
相关产品推荐

