Flutter如何实现容器从左到右的填充式颜色切换动画(仿Netflix“下一集”效果)
嘿,这个需求我刚好做过类似的!AnimatedContainer确实没法直接实现这种渐进式填充效果,但我们可以用Stack配合动画组件来搞定,完美还原Netflix“下一集”的那种左到右填充动画~
核心实现思路
用Stack将两个容器层叠:
- 底层是你需要的初始色容器(比如白色),占满整个空间
- 上层是目标色容器(比如红色),通过动画控制它的宽度从
0过渡到容器的总宽度,视觉上就呈现出红色从左到右“填充”的效果
基础实现代码(简单版)
这个版本用AnimatedContainer就能快速实现,适合不需要复杂动画曲线的场景:
class FillAnimationContainer extends StatefulWidget { const FillAnimationContainer({super.key}); @override State<FillAnimationContainer> createState() => _FillAnimationContainerState(); } class _FillAnimationContainerState extends State<FillAnimationContainer> { // 控制红色填充的宽度,初始为0 double _fillWidth = 0; // 动画时长,可根据需求调整 final Duration _animationDuration = const Duration(seconds: 2); // 触发填充动画的方法 void startFillAnimation() { setState(() { // 把宽度设为无限大,等同于占满底层容器 _fillWidth = double.infinity; }); } @override Widget build(BuildContext context) { return GestureDetector( // 点击触发动画,你也可以在页面加载时自动调用startFillAnimation() onTap: startFillAnimation, child: Container( width: 300, height: 60, // 外层容器加圆角,避免红色填充溢出 decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Stack( children: [ // 底层白色容器(初始底色) Container( width: double.infinity, height: double.infinity, color: Colors.white, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), ), ), // 上层红色填充容器,用AnimatedContainer实现宽度动画 AnimatedContainer( duration: _animationDuration, width: _fillWidth, color: Colors.red, // 和外层容器保持一致的圆角,只保留左侧圆角更自然 decoration: const BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(8), bottomLeft: Radius.circular(8), ), ), ), // 叠加文字(可选,模拟Netflix的"下一集"提示) const Center( child: Text( "下一集", style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w500), ), ), ], ), ), ); } }
进阶实现(自定义动画曲线)
如果需要更贴合Netflix的缓入缓出动画效果,可以用AnimationController搭配CurvedAnimation来精细控制:
class AdvancedFillAnimation extends StatefulWidget { const AdvancedFillAnimation({super.key}); @override State<AdvancedFillAnimation> createState() => _AdvancedFillAnimationState(); } class _AdvancedFillAnimationState extends State<AdvancedFillAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _widthAnimation; final Duration _animationDuration = const Duration(seconds: 2); final double _containerWidth = 300; @override void initState() { super.initState(); // 初始化动画控制器 _controller = AnimationController( duration: _animationDuration, vsync: this, ); // 设置动画曲线,Curves.easeInOut和Netflix的动画质感接近 _widthAnimation = Tween<double>(begin: 0, end: _containerWidth).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } @override void dispose() { // 记得销毁控制器,避免内存泄漏 _controller.dispose(); super.dispose(); } void startFillAnimation() { _controller.forward(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: startFillAnimation, child: Container( width: _containerWidth, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Stack( children: [ const Container( width: double.infinity, height: double.infinity, color: Colors.white, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(8)), ), ), // 用AnimatedBuilder监听动画值变化 AnimatedBuilder( animation: _widthAnimation, builder: (context, child) { return Container( width: _widthAnimation.value, color: Colors.red, decoration: const BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(8), bottomLeft: Radius.circular(8), ), ), ); }, ), const Center( child: Text( "下一集", style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w500), ), ), ], ), ), ); } }
关键细节提示
- 如果需要圆角效果,务必让红色填充容器和外层容器的圆角保持一致,只保留左侧圆角会让填充动画更自然
- 动画时长可以根据需求调整,Netflix的下一集动画大概在1.5-2秒左右
- 可以通过
_controller.reverse()实现反向填充动画,或者_controller.repeat()让动画循环播放
内容的提问来源于stack exchange,提问作者badrobot15
相关产品推荐
相关产品推荐

