如何使用Flutter实现iOS Control Center弹窗容器展开动画
Flutter实现弹窗内容器展开动画(类似iOS控制中心音乐播放器)
核心实现思路
通过AnimationController控制动画时长与状态,结合Transform.scale或AnimatedContainer实现容器尺寸的平滑过渡,搭配弹性曲线模拟原生系统的灵动效果,同时可叠加透明度动画增强视觉层次。
完整示例代码
import 'package:flutter/material.dart'; void main() => runApp(const ExpandablePopupDemo()); class ExpandablePopupDemo extends StatelessWidget { const ExpandablePopupDemo({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( backgroundColor: Colors.grey[900], body: const Center(child: ExpandablePlayerCard()), ), ); } } class ExpandablePlayerCard extends StatefulWidget { const ExpandablePlayerCard({super.key}); @override State<ExpandablePlayerCard> createState() => _ExpandablePlayerCardState(); } class _ExpandablePlayerCardState extends State<ExpandablePlayerCard> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation; late Animation<double> _opacityAnimation; bool _isExpanded = false; @override void initState() { super.initState(); // 初始化动画控制器,时长匹配iOS原生动画节奏 _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); // 缩放动画:从原始尺寸放大到2.5倍 _scaleAnimation = Tween<double>(begin: 1.0, end: 2.5).animate( CurvedAnimation(parent: _controller, curve: Curves.elasticOut), ); // 透明度动画:收起时半透明,展开后完全显示 _opacityAnimation = Tween<double>(begin: 0.8, end: 1.0).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } void _toggleExpand() { setState(() { _isExpanded = !_isExpanded; _isExpanded ? _controller.forward() : _controller.reverse(); }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _toggleExpand, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Opacity( opacity: _opacityAnimation.value, child: Container( width: 200, height: 120, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[800], borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 5), ) ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.music_note, color: Colors.white, size: 32), const SizedBox(height: 8), Text( _isExpanded ? "正在播放:Flutter BGM" : "点击展开", style: const TextStyle(color: Colors.white, fontSize: 14), textAlign: TextAlign.center, ), // 展开后显示播放控制按钮 if (_isExpanded) const Padding( padding: EdgeInsets.only(top: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Icon(Icons.skip_previous, color: Colors.white), Icon(Icons.pause_circle, color: Colors.white, size: 32), Icon(Icons.skip_next, color: Colors.white), ], ), ) ], ), ), ), ); }, ), ); } }
关键细节说明
- 动画曲线选择:示例使用
Curves.elasticOut实现弹性回弹效果,和iOS控制中心的动画质感匹配;偏好平缓效果可替换为Curves.easeInOut。 - 尺寸控制方案:除了
Transform.scale,也可以用AnimatedContainer直接修改width和height属性,两种方式都能实现展开/收起效果。 - 背景模糊优化:要模拟iOS的背景模糊,可在容器外层嵌套
BackdropFilter,搭配ImageFilter.blur(sigmaX: 5, sigmaY: 5)实现。 - 状态切换逻辑:通过
_isExpanded变量控制状态,点击时触发动画控制器的forward()(展开)或reverse()(收起)方法。
内容的提问来源于stack exchange,提问作者Valentyna P
相关产品推荐
相关产品推荐

