Flutter动画优化:单AnimationController实现多动画并单独反转指定动画
问题描述
我想实现用单个AnimationController控制多个动画,并且能单独反转该控制器中的指定动画。目前我用了多个AnimationController,每个单独控制一个动画,代码如下:
late final AnimationController controllerHeightCard, controllerHeightNativeView, controllerInsertingView; void heightAnimation1() { controllerHeightCard = AnimationController( duration: const Duration(milliseconds: 250), vsync: this); controllerInsertingView = AnimationController( duration: const Duration(milliseconds: 250), vsync: this); heightAnimation_1 = Tween(begin: 310.0, end: 380.0).animate(controllerHeightCard); heightAnimation_1.addListener(() { cardHeight.value = heightAnimation_1.value; }); heightAnimation_2 = Tween(begin: 0.0, end: 75.0).animate(controllerHeightNativeView); heightAnimation_2.addListener(() { containerHeight.value = heightAnimation_2.value; }); controllerHeightCard.addListener(() { if (controllerHeightCard.isCompleted && isSignUp.value) { controllerHeightNativeView.forward(); } }); controllerHeightNativeView.addStatusListener((status) { if (controllerHeightNativeView.isDismissed && isSignUp.value == false) { controllerHeightCard.reverse(); } }); } heightAnimationForward() { isSignUp.value = !isSignUp.value; if (isSignUp.value == true) { controllerHeightCard.forward(); } if (isSignUp.value == false) { controllerHeightNativeView.reverse(from: 0.1); } }
请问该怎么实现这个需求?PS:我是Flutter新手,代码写得不好请见谅。
解决方案
1. 核心思路
用单个AnimationController作为统一驱动源,给每个动画创建独立的Animation对象绑定到该控制器。要实现单独反转指定动画,可通过自定义动画值计算的方式,用布尔标记控制单个动画的方向,无需额外控制器。
2. 改造后的完整代码
late final AnimationController _mainController; // 用布尔标记控制单个动画的反转状态 bool _reverseCardAnim = false; bool _reverseNativeViewAnim = false; final ValueNotifier<double> cardHeight = ValueNotifier(310.0); final ValueNotifier<double> containerHeight = ValueNotifier(0.0); final ValueNotifier<bool> isSignUp = ValueNotifier(false); void initAnimations() { // 单个主控制器,统一管理动画时长 _mainController = AnimationController( duration: const Duration(milliseconds: 250), vsync: this, ); // 监听控制器进度,手动计算每个动画的当前值(支持单独反转) _mainController.addListener(() { final progress = _mainController.value; // 卡片高度:根据反转状态计算值 final currentCardHeight = _reverseCardAnim ? 380.0 - 70.0 * progress // 反向:从380降到310 : 310.0 + 70.0 * progress; // 正向:从310升到380 cardHeight.value = currentCardHeight; // NativeView高度:根据反转状态计算值 final currentNativeHeight = _reverseNativeViewAnim ? 75.0 - 75.0 * progress // 反向:从75降到0 : 75.0 * progress; // 正向:从0升到75 containerHeight.value = currentNativeHeight; }); // 保留原有联动逻辑:动画状态监听 _mainController.addStatusListener((status) { if (status == AnimationStatus.completed && isSignUp.value) { // 卡片动画完成后,启动NativeView正向动画 _reverseNativeViewAnim = false; _mainController.reset(); _mainController.forward(); } if (status == AnimationStatus.dismissed && !isSignUp.value) { // NativeView动画消失后,启动卡片反向动画 _reverseCardAnim = true; _mainController.reset(); _mainController.forward(); } }); } void toggleAnimation() { isSignUp.value = !isSignUp.value; if (isSignUp.value) { // 切换到注册状态:正向执行卡片动画 _reverseCardAnim = false; _mainController.reset(); _mainController.forward(); } else { // 切换到非注册状态:反向执行NativeView动画 _reverseNativeViewAnim = true; _mainController.reset(); _mainController.forward(); } } // 记得在页面销毁时释放控制器 @override void dispose() { _mainController.dispose(); super.dispose(); }
3. 关键说明
- 单控制器驱动:所有动画共享
_mainController,减少资源占用,逻辑更集中。 - 单独反转控制:通过
_reverseCardAnim、_reverseNativeViewAnim两个布尔标记,在控制器监听中手动计算每个动画的当前值,实现单个动画的反转效果。 - 联动逻辑保留:通过控制器的
addStatusListener,保留了你原有代码中动画完成/消失后的联动逻辑。
4. 进阶优化:用AnimatedBuilder简化UI更新
如果你的UI是用AnimatedBuilder构建的,可以去掉ValueNotifier,让Flutter自动根据动画进度重建UI,性能更优:
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _mainController, builder: (context, child) { final progress = _mainController.value; final cardHeight = _reverseCardAnim ? 380.0 - 70.0 * progress : 310.0 + 70.0 * progress; final nativeHeight = _reverseNativeViewAnim ? 75.0 - 75.0 * progress : 75.0 * progress; return Column( children: [ Container( height: cardHeight, color: Colors.blue, child: child, ), Container( height: nativeHeight, color: Colors.red, ), ], ); }, child: const Text('Card Content'), // 无需动画的子部件,避免重复重建 ); }
内容的提问来源于stack exchange,提问作者LastMomentCoder
相关产品推荐
相关产品推荐

