在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

概述

路由动画组合是将多个动画效果结合起来使用的技术。通过组合不同的动画效果,可以创造出更加丰富和生动的页面切换体验。本章将详细介绍路由动画组合的实现方法、常见组合模式和最佳实践。

核心概念

动画组合的类型

动画组合主要分为以下几种类型:

组合类型 说明 适用场景
同时组合 多个动画同时执行 创建复杂的进入效果
顺序组合 动画按顺序执行 创造层次感
交错组合 动画在不同时间点开始 增加动态感

组合动画的优势

  1. 视觉丰富:单一动画效果有限,组合动画可以创造出更多样化的视觉效果
  2. 层次分明:通过顺序或交错组合,可以创造出层次感
  3. 用户引导:组合动画可以引导用户注意力到关键元素上
  4. 品牌特色:独特的组合动画可以成为应用的品牌特色

同时组合动画

基本实现

同时组合动画是最常见的组合方式,多个动画效果同时应用到同一个子组件上。

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(1.0, 0.0),
          end: Offset.zero,
        ).animate(animation),
        child: FadeTransition(
          opacity: animation,
          child: child,
        ),
      );
    },
  ),
);

多种效果同时应用

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return ScaleTransition(
        scale: Tween<double>(begin: 0.8, end: 1.0).animate(animation),
        child: RotationTransition(
          turns: Tween<double>(begin: 0.1, end: 0.0).animate(animation),
          child: FadeTransition(
            opacity: animation,
            child: child,
          ),
        ),
      );
    },
  ),
);

可配置的同时组合

通过封装,可以创建一个可配置的同时组合动画工具类:

enum AnimationEffect {
  slide,
  fade,
  scale,
  rotate,
}

class RouteAnimationComposition {
  static Route composeMultipleAnimations(
    BuildContext context,
    Widget page, {
    List<AnimationEffect> effects = const [AnimationEffect.slide, AnimationEffect.fade],
    Duration duration = const Duration(milliseconds: 300),
    Curve curve = Curves.ease,
  }) {
    return PageRouteBuilder(
      pageBuilder: (context, animation, secondaryAnimation) => page,
      transitionDuration: duration,
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        Widget result = child;
        
        for (var effect in effects) {
          result = _applyEffect(effect, animation, result);
        }
        
        return result;
      },
    );
  }

  static Widget _applyEffect(AnimationEffect effect, Animation<double> animation, Widget child) {
    switch (effect) {
      case AnimationEffect.slide:
        return SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(1.0, 0.0),
            end: Offset.zero,
          ).animate(animation),
          child: child,
        );
      case AnimationEffect.fade:
        return FadeTransition(
          opacity: animation,
          child: child,
        );
      case AnimationEffect.scale:
        return ScaleTransition(
          scale: Tween<double>(begin: 0.8, end: 1.0).animate(animation),
          child: child,
        );
      case AnimationEffect.rotate:
        return RotationTransition(
          turns: Tween<double>(begin: 0.1, end: 0.0).animate(animation),
          child: child,
        );
      default:
        return child;
    }
  }
}

使用方法

Navigator.push(
  context,
  RouteAnimationComposition.composeMultipleAnimations(
    context,
    const DetailPage(),
    effects: [AnimationEffect.slide, AnimationEffect.fade, AnimationEffect.scale],
    duration: const Duration(milliseconds: 400),
  ),
);

顺序组合动画

使用 Interval 创建顺序动画

Interval 可以控制动画的执行时间段,通过设置不同的区间,可以实现顺序动画效果。

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      var slideAnimation = CurvedAnimation(
        parent: animation,
        curve: const Interval(0.0, 0.6, curve: Curves.ease),
      );
      
      var fadeAnimation = CurvedAnimation(
        parent: animation,
        curve: const Interval(0.4, 1.0, curve: Curves.ease),
      );

      return FadeTransition(
        opacity: fadeAnimation,
        child: SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(1.0, 0.0),
            end: Offset.zero,
          ).animate(slideAnimation),
          child: child,
        ),
      );
    },
  ),
);

完整的顺序动画示例

static Route sequentialAnimation(BuildContext context, Widget page) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      var delayedAnimation = CurvedAnimation(
        parent: animation,
        curve: const Interval(0.2, 1.0, curve: Curves.ease),
      );

      return SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(1.0, 0.0),
          end: Offset.zero,
        ).animate(delayedAnimation),
        child: FadeTransition(
          opacity: animation,
          child: child,
        ),
      );
    },
  );
}

顺序动画的时间控制

时间段 动画效果 说明
0.0-0.2 延迟阶段
0.2-1.0 滑动 + 淡入 动画执行阶段

交错组合动画

使用多个 Interval 创建交错效果

交错组合动画通过设置不同的开始和结束时间,让不同的动画效果在不同时间点开始和结束。

static Route staggeredAnimation(BuildContext context, Widget page) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(
        opacity: animation,
        child: SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(0, 0.3),
            end: Offset.zero,
          ).animate(
            CurvedAnimation(
              parent: animation,
              curve: const Interval(0.1, 1.0, curve: Curves.easeOut),
            ),
          ),
          child: ScaleTransition(
            scale: Tween<double>(begin: 0.9, end: 1.0).animate(
              CurvedAnimation(
                parent: animation,
                curve: const Interval(0.2, 1.0, curve: Curves.easeOut),
              ),
            ),
            child: child,
          ),
        ),
      );
    },
  );
}

交错动画的时间线

时间线: 0.0  0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1.0
        |----|----|----|----|----|----|----|----|----|----|
淡入:   |<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<|
滑动:       |<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<|
缩放:           |<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<|

复杂交错动画示例

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return RotationTransition(
        turns: Tween<double>(begin: -0.1, end: 0.0).animate(
          CurvedAnimation(
            parent: animation,
            curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
          ),
        ),
        child: ScaleTransition(
          scale: Tween<double>(begin: 0.7, end: 1.0).animate(
            CurvedAnimation(
              parent: animation,
              curve: const Interval(0.2, 0.8, curve: Curves.elasticOut),
            ),
          ),
          child: FadeTransition(
            opacity: animation,
            child: SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(0.0, -0.2),
                end: Offset.zero,
              ).animate(
                CurvedAnimation(
                  parent: animation,
                  curve: const Interval(0.3, 1.0, curve: Curves.easeOut),
                ),
              ),
              child: child,
            ),
          ),
        ),
      );
    },
    transitionDuration: const Duration(milliseconds: 500),
  ),
);

动画组合的最佳实践

选择合适的组合方式

场景 推荐组合方式 说明
普通页面切换 同时组合(slide + fade) 经典组合,效果稳定
详情页面 同时组合(scale + fade) 突出内容
设置页面 顺序组合(slide up) 引导用户注意力
特殊效果页面 交错组合 创造独特体验

控制动画数量

  • 建议数量:2-3个动画效果
  • 避免过多:过多动画会让页面显得混乱
  • 保持简洁:聚焦于核心效果

动画时长的设置

组合类型 推荐时长 说明
简单组合 200-300ms 快速响应
中等组合 300-400ms 平衡效果和性能
复杂组合 400-500ms 充分展示效果

使用缓动曲线

static Route composeWithCurve(BuildContext context, Widget page) {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      var curvedAnimation = CurvedAnimation(
        parent: animation,
        curve: Curves.easeOutCubic,
        reverseCurve: Curves.easeInCubic,
      );

      return SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(1.0, 0.0),
          end: Offset.zero,
        ).animate(curvedAnimation),
        child: FadeTransition(
          opacity: curvedAnimation,
          child: child,
        ),
      );
    },
    transitionDuration: const Duration(milliseconds: 350),
  );
}

性能优化

使用 RepaintBoundary

对于复杂的组合动画,使用 RepaintBoundary 可以减少不必要的重绘:

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) {
      return RepaintBoundary(
        child: const DetailPage(),
      );
    },
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(1.0, 0.0),
          end: Offset.zero,
        ).animate(animation),
        child: FadeTransition(
          opacity: animation,
          child: child,
        ),
      );
    },
  ),
);

避免过度动画

  • 不要在每个页面切换都使用复杂的组合动画
  • 只在关键页面使用特殊效果
  • 提供关闭动画的选项

使用 AnimatedBuilder

对于需要自定义动画逻辑的场景,使用 AnimatedBuilder

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          double value = animation.value;
          return Transform(
            transform: Matrix4.translationValues(
              value * MediaQuery.of(context).size.width,
              0,
              0,
            ),
            child: Opacity(
              opacity: value,
              child: child,
            ),
          );
        },
        child: child,
      );
    },
  ),
);

实际应用场景

社交应用首页

Navigator.push(
  context,
  RouteAnimationComposition.composeMultipleAnimations(
    context,
    const FeedDetailPage(),
    effects: [AnimationEffect.slide, AnimationEffect.fade],
    duration: const Duration(milliseconds: 300),
  ),
);

设置页面

Navigator.push(
  context,
  RouteAnimationComposition.sequentialAnimation(
    context,
    const SettingsPage(),
  ),
);

图片详情页

Navigator.push(
  context,
  RouteAnimationComposition.staggeredAnimation(
    context,
    const ImageDetailPage(),
  ),
);

总结

路由动画组合是创造丰富用户体验的重要技术。通过掌握同时组合、顺序组合和交错组合三种方式,开发者可以根据不同场景选择合适的动画组合策略。在实际应用中,需要注意控制动画数量、合理设置时长、选择合适的缓动曲线,并考虑性能优化。组合动画不仅能提升应用的视觉效果,还能引导用户注意力,创造独特的品牌体验。

常见问题

Q1: 组合动画会影响性能吗?

A: 是的,组合动画会增加渲染开销。建议:

  • 使用 RepaintBoundary 减少重绘
  • 控制动画数量在2-3个
  • 设置合理的动画时长

Q2: 如何调试组合动画?

A: 可以通过以下方式调试:

  • 使用 Flutter DevTools 的动画检查器
  • 添加打印语句观察动画值变化
  • 分步实现,逐步添加动画效果

Q3: 组合动画在不同平台上表现一致吗?

A: 基本一致,但需要注意:

  • iOS 和 Android 的默认动画风格不同
  • 可以根据平台选择不同的动画组合
  • 使用 Theme.of(context).platform 判断平台

Q4: 如何实现退出时的组合动画?

A: 使用 secondaryAnimation 实现退出动画:

transitionsBuilder: (context, animation, secondaryAnimation, child) {
  var enterTransition = SlideTransition(
    position: Tween<Offset>(
      begin: const Offset(1.0, 0.0),
      end: Offset.zero,
    ).animate(animation),
    child: FadeTransition(
      opacity: animation,
      child: child,
    ),
  );

  var exitTransition = SlideTransition(
    position: Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(-0.2, 0.0),
    ).animate(secondaryAnimation),
    child: FadeTransition(
      opacity: ReverseAnimation(secondaryAnimation),
      child: enterTransition,
    ),
  );

  return exitTransition;
},
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐