Flutter连续动画实现:第二个动画从首个动画终点启动的方法
Flutter实现连续的衔接式动画(从上个动画结束值开始)
方案一:使用TweenSequence实现(一次性定义连续动画)
如果你之前用TweenSequence没成功,大概率是没给每个阶段的补间正确绑定曲线,或者没做好值的衔接。以下是正确的实现方式:
代码示例
import 'package:flutter/material.dart'; class CarSpeedAnimation extends StatefulWidget { const CarSpeedAnimation({super.key}); @override State<CarSpeedAnimation> createState() => _CarSpeedAnimationState(); } class _CarSpeedAnimationState extends State<CarSpeedAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _speedAnimation; @override void initState() { super.initState(); // 总动画时长:前2秒完成0→50,后2秒完成50→70 _controller = AnimationController( duration: const Duration(seconds: 4), vsync: this, ); _speedAnimation = TweenSequence<double>([ // 第一阶段:0→50,使用Curves.easeIn曲线 TweenSequenceItem( tween: Tween<double>(begin: 0, end: 50).chain(CurveTween(curve: Curves.easeIn)), weight: 1.0, // 权重决定占总时长的比例,和下一个1.0平分总时长 ), // 第二阶段:50→70,同样使用Curves.easeIn曲线 TweenSequenceItem( tween: Tween<double>(begin: 50, end: 70).chain(CurveTween(curve: Curves.easeIn)), weight: 1.0, ), ]).animate(_controller); // 启动动画 _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: AnimatedBuilder( animation: _speedAnimation, builder: (context, child) { return Text( '当前速度:${_speedAnimation.value.toStringAsFixed(1)} mph', style: const TextStyle(fontSize: 24), ); }, ), ), ); } }
关键说明
- 每个
TweenSequenceItem的tween需要通过chain(CurveTween(curve: Curves.easeIn))单独绑定曲线,确保每个阶段都使用easeIn动画曲线。 weight参数控制各阶段动画的时长占比,比如两个阶段都设为1.0,就会平分总时长。- 两个阶段的
begin和end要严格衔接,第一阶段的end等于第二阶段的begin,保证动画平滑过渡。
方案二:分阶段触发动画(更灵活的控制方式)
如果需要在两个动画之间加入延迟、用户交互等逻辑,分阶段触发的方式会更合适。可以用一个AnimationController来实现:
代码示例
import 'package:flutter/material.dart'; class CarSpeedAnimation extends StatefulWidget { const CarSpeedAnimation({super.key}); @override State<CarSpeedAnimation> createState() => _CarSpeedAnimationState(); } class _CarSpeedAnimationState extends State<CarSpeedAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _speedAnimation; double _currentSpeed = 0; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); // 初始化第一个动画:0→50 startFirstAnimation(); _controller.forward(); } void startFirstAnimation() { _speedAnimation = Tween<double>(begin: 0, end: 50) .chain(CurveTween(curve: Curves.easeIn)) .animate(_controller) ..addListener(() { setState(() { _currentSpeed = _speedAnimation.value; }); }) ..addStatusListener((status) { if (status == AnimationStatus.completed) { // 第一个动画完成后,启动第二个动画:50→70 _controller.reset(); startSecondAnimation(); _controller.forward(); } }); } void startSecondAnimation() { _speedAnimation = Tween<double>(begin: 50, end: 70) .chain(CurveTween(curve: Curves.easeIn)) .animate(_controller) ..addListener(() { setState(() { _currentSpeed = _speedAnimation.value; }); }); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text( '当前速度:${_currentSpeed.toStringAsFixed(1)} mph', style: const TextStyle(fontSize: 24), ), ), ); } }
关键说明
- 通过
AnimationStatus.completed监听第一个动画的完成状态,触发第二个动画。 - 重置Controller后,重新创建从50到70的补间动画,保证第二个动画从第一个的结束值开始。
- 如果需要在两个动画之间加入延迟,可以在触发第二个动画前加入
Future.delayed(const Duration(seconds: 1))。
方案三:双Controller版本(更清晰的状态分离)
如果不想复用Controller,也可以用两个独立的Controller分别控制两个动画,状态更清晰:
import 'package:flutter/material.dart'; class CarSpeedAnimation extends StatefulWidget { const CarSpeedAnimation({super.key}); @override State<CarSpeedAnimation> createState() => _CarSpeedAnimationState(); } class _CarSpeedAnimationState extends State<CarSpeedAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller1; late AnimationController _controller2; late Animation<double> _speedAnimation1; late Animation<double> _speedAnimation2; double _currentSpeed = 0; @override void initState() { super.initState(); _controller1 = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _controller2 = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); // 第一个动画:0→50 _speedAnimation1 = Tween<double>(begin: 0, end: 50) .chain(CurveTween(curve: Curves.easeIn)) .animate(_controller1) ..addListener(() { setState(() { _currentSpeed = _speedAnimation1.value; }); }) ..addStatusListener((status) { if (status == AnimationStatus.completed) { // 第一个动画完成,启动第二个动画 _controller2.forward(); } }); // 第二个动画:50→70 _speedAnimation2 = Tween<double>(begin: 50, end: 70) .chain(CurveTween(curve: Curves.easeIn)) .animate(_controller2) ..addListener(() { setState(() { _currentSpeed = _speedAnimation2.value; }); }); // 启动第一个动画 _controller1.forward(); } @override void dispose() { _controller1.dispose(); _controller2.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text( '当前速度:${_currentSpeed.toStringAsFixed(1)} mph', style: const TextStyle(fontSize: 24), ), ), ); } }
关键说明
- 两个Controller独立控制各自的动画,避免复用Controller可能带来的状态混乱。
- 同样通过第一个动画的完成状态监听来触发第二个动画。
内容的提问来源于stack exchange,提问作者user1111
相关产品推荐
相关产品推荐

