🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx @anthropic-ai/skills install madteacher/mad-agents-skills/flutter-animations

💡 提示:需要 Node.js 和 NPM

Flutter 动画

概述

在 Flutter 中为每个用例选择正确的方法,创建流畅、高性能的动画。本技能涵盖了完整的动画工作流程:从隐式/显式方法的选择,到实现复杂的特效,如 Hero 过渡和交错动画。

动画类型决策树

根据你的需求选择合适的动画类型:

隐式动画 – 适用于以下情况:

  • 对单个属性(颜色、大小、位置)进行动画处理
  • 动画由状态变化触发
  • 无需精细控制

显式动画 – 适用于以下情况:

  • 需要完全控制动画生命周期
  • 同时动画化多个属性
  • 需要对动画状态变化做出反应
  • 创建自定义动画或过渡

Hero 动画 – 适用于以下情况:

  • 在两个屏幕之间共享一个元素
  • 创建共享元素过渡
  • 用户期望元素在路由之间“飞动”

交错动画 – 适用于以下情况:

  • 多个动画应按顺序运行或重叠运行
  • 创建涟漪效果或顺序揭示
  • 按顺序动画化列表项

基于物理的动画 – 适用于以下情况:

  • 动画应感觉自然/符合物理规律
  • 类似弹簧的行为、滚动手势
  • 可拖拽交互

隐式动画

隐式动画在属性发生变化时自动处理动画。无需控制器。

常用隐式动画组件

AnimatedContainer – 动画化多个属性(大小、颜色、装饰、内边距):

AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: _expanded ? 200 : 100,
  height: _expanded ? 200 : 100,
  color: _expanded ? Colors.blue : Colors.red,
  child: const FlutterLogo(),
)

AnimatedOpacity – 简单的淡入淡出动画:

AnimatedOpacity(
  opacity: _visible ? 1.0 : 0.0,
  duration: const Duration(milliseconds: 300),
  child: const Text('你好'),
)

TweenAnimationBuilder – 无需样板代码的自定义补间动画:

TweenAnimationBuilder<double>(
  tween: Tween<double>(begin: 0, end: 1),
  duration: const Duration(seconds: 1),
  builder: (context, value, child) {
    return Opacity(
      opacity: value,
      child: Transform.scale(
        scale: value,
        child: child,
      ),
    );
  },
  child: const FlutterLogo(),
)

其他隐式动画组件:

  • AnimatedPadding – 内边距动画
  • AnimatedPositioned – 位置动画(在 Stack 中)
  • AnimatedAlign – 对齐方式动画
  • AnimatedContainer – 多个属性动画
  • AnimatedSwitcher – 组件之间的交叉淡入淡出
  • AnimatedDefaultTextStyle – 文本样式动画

最佳实践

  • 对于简单情况,优先使用隐式动画
  • 使用适当的曲线实现自然运动(参见 Curves 类)
  • 设置 curveduration 以实现可预测的行为
  • 在需要时使用 onEnd 回调
  • 避免嵌套隐式动画以优化性能

显式动画

显式动画通过 AnimationController 提供完全控制。

核心组件

AnimationController – 驱动动画:

late AnimationController _controller;


void initState() {
  super.initState();
  _controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  );
}


void dispose() {
  _controller.dispose();
  super.dispose();
}

Tween – 在起始值和结束值之间插值:

animation = Tween<double>(begin: 0, end: 300).animate(_controller);

CurvedAnimation – 为动画应用曲线:

animation = CurvedAnimation(
  parent: _controller,
  curve: Curves.easeInOut,
);

AnimatedWidget 模式

最适合可重用的动画组件:

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
    : super(listenable: animation);

  
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}

AnimatedBuilder 模式

最适合带有动画的复杂组件:

class GrowTransition extends StatelessWidget {
  const GrowTransition({
    required this.child,
    required this.animation,
    super.key,
  });

  final Widget child;
  final Animation<double> animation;

  
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}

监控动画状态

animation.addStatusListener((status) {
  switch (status) {
    case AnimationStatus.completed:
      _controller.reverse();
      break;
    case AnimationStatus.dismissed:
      _controller.forward();
      break;
    default:
      break;
  }
});

多个同时进行的动画

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
    : super(listenable: animation);

  static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
  static final _sizeTween = Tween<double>(begin: 0, end: 300);

  
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: Container(
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: const FlutterLogo(),
        ),
      ),
    );
  }
}

内置显式过渡

Flutter 提供了现成的过渡组件:

  • FadeTransition – 淡入淡出动画
  • ScaleTransition – 缩放动画
  • SlideTransition – 滑动动画
  • SizeTransition – 大小动画
  • RotationTransition – 旋转动画
  • PositionedTransition – 位置动画(在 Stack 中)

示例:

FadeTransition(
  opacity: _animation,
  child: const FlutterLogo(),
)

性能提示

  • 当组件被移除时,释放控制器
  • 使用 AnimatedBuilder 实现最优重建
  • 避免在动画监听器中使用 setState()(使用 AnimatedWidget/AnimatedBuilder
  • 在调试期间使用 timeDilation 来减慢动画速度

Hero 动画

Hero 动画在屏幕之间创建共享元素过渡。

基础 Hero 动画

源屏幕:

Hero(
  tag: 'hero-image',
  child: Image.asset('images/logo.png'),
)

目标屏幕:

Hero(
  tag: 'hero-image',  // 相同的标签!
  child: Image.asset('images/logo.png'),
)

完整示例

class PhotoHero extends StatelessWidget {
  const PhotoHero({
    super.key,
    required this.photo,
    this.onTap,
    required this.width,
  });

  final String photo;
  final VoidCallback? onTap;
  final double width;

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      child: Hero(
        tag: photo,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onTap,
            child: Image.asset(photo, fit: BoxFit.contain),
          ),
        ),
      ),
    );
  }
}

在屏幕之间导航:

Navigator.of(context).push(
  MaterialPageRoute<void>(
    builder: (context) {
      return Scaffold(
        appBar: AppBar(title: const Text('详情')),
        body: Center(
          child: PhotoHero(
            photo: 'images/logo.png',
            width: 300.0,
            onTap: () => Navigator.of(context).pop(),
          ),
        ),
      );
    },
  ),
);

径向 Hero 动画

在过渡期间从圆形变换为矩形:

class RadialExpansion extends StatelessWidget {
  const RadialExpansion({
    super.key,
    required this.maxRadius,
    this.child,
  }) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);

  final double maxRadius;
  final double clipRectSize;
  final Widget? child;

  
  Widget build(BuildContext context) {
    return ClipOval(
      child: Center(
        child: SizedBox(
          width: clipRectSize,
          height: clipRectSize,
          child: ClipRect(child: child),
        ),
      ),
    );
  }
}

MaterialRectCenterArcTween 结合使用,实现基于中心的插值:

static RectTween _createRectTween(Rect? begin, Rect? end) {
  return MaterialRectCenterArcTween(begin: begin, end: end);
}

Hero 最佳实践

  • 使用唯一且一致的标签(通常是数据对象本身)
  • 保持路由之间的 hero 组件树相似
  • 将图片包装在具有透明颜色的 Material 中,以实现”弹出”效果
  • 使用 timeDilation 调试过渡
  • 在需要时考虑使用 HeroMode 禁用 hero 动画

交错动画

使用不同的时序运行多个动画。

基础交错动画

所有动画共享一个控制器:

class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({super.key, required this.controller})
    : opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(
          parent: controller,
          curve: const Interval(0.0, 0.100, curve: Curves.ease),
        ),
      ),
      width = Tween<double>(begin: 50.0, end: 150.0).animate(
        CurvedAnimation(
          parent: controller,
          curve: const Interval(0.125, 0.250, curve: Curves.ease),
        ),
      );

  final AnimationController controller;
  final Animation<double> opacity;
  final Animation<double> width;

  Widget _buildAnimation(BuildContext context, Widget? child) {
    return Container(
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: 150,
          color: Colors.blue,
        ),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: _buildAnimation,
    );
  }
}

基于区间的时序

每个动画在 0.0 到 1.0 之间有一个区间:

animation = Tween<double>(begin: 0, end: 300).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(
      0.25,  // 在控制器持续时间的 25% 时开始
      0.50,  // 在控制器持续时间的 50% 时结束
      curve: Curves.ease,
    ),
  ),
);

常用补间

borderRadius = BorderRadiusTween(
  begin: BorderRadius.circular(4),
  end: BorderRadius.circular(75),
).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(0.375, 0.500, curve: Curves.ease),
  ),
);

交错菜单动画

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  static const _initialDelayTime = Duration(milliseconds: 50);
  static const _itemSlideTime = Duration(milliseconds: 250);
  static const _staggerTime = Duration(milliseconds: 50);
  static const _buttonDelayTime = Duration(milliseconds: 150);
  static const _buttonTime = Duration(milliseconds: 500);

  final _animationDuration =
      _initialDelayTime +
      (_staggerTime * _menuTitles.length) +
      _buttonDelayTime +
      _buttonTime;

  late AnimationController _controller;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: _animationDuration,
      vsync: this,
    );
    _controller.forward();
  }

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

交错最佳实践

  • 使用 Interval 在时间上偏移动画
  • 确保控制器持续时间覆盖所有区间
  • 在区间内使用曲线实现自然运动
  • 考虑使用 timeDilation 调试时序
  • 对于菜单项,使用递增延迟实现涟漪效果

基于物理的动画

使用物理模拟创建自然感觉的动画。

Fling 动画

_controller.fling(
  velocity: 2.0,  // 单位每秒
);

自定义物理模拟

_controller.animateWith(
  SpringSimulation(
    spring: const SpringDescription(
      mass: 1,
      stiffness: 100,
      damping: 10,
    ),
    start: 0.0,
    end: 1.0,
    velocity: 0.0,
  ),
);

常用物理模拟

  • SpringSimulation – 弹簧物理
  • BouncingScrollSimulation – 带弹性的滚动
  • ClampingScrollSimulation – 无弹性的滚动
  • GravitySimulation – 基于重力的模拟

最佳实践

要做的

  • 在组件销毁时释放 AnimationController
  • 使用 AnimatedBuilder/AnimatedWidget 而不是在监听器中使用 setState()
  • 选择适当的曲线以实现自然运动
  • 使用 timeDilation 调试动画
  • 考虑性能(避免在动画构建中使用重型组件)
  • 在各种设备上测试动画
  • 支持反向动画以实现直观感受

不要做的

  • 忘记释放 AnimationController(会导致内存泄漏)
  • AnimatedBuilder 足够的情况下,在动画监听器中使用 setState()
  • 假设动画会立即完成(需处理 AnimationStatus
  • 过度动画化(动画可能会分散用户注意力)
  • 创建感觉“卡顿”的动画(使用平滑的曲线)
  • 忽略可访问性(尊重 disableAnimations 偏好)

资源

references/

implicit.md – 包含示例和最佳实践的隐式动画组件完整参考。

explicit.md – 深入探讨显式动画、AnimationController 和模式。

hero.md – Hero 动画指南,包含标准和径向过渡。

staggered.md – 交错动画模式和时序策略。

physics.md – 基于物理的动画和模拟。

curves.md – Curves 类参考以及如何选择合适的曲线。

assets/templates/

常用动画模式的模板代码:

  • implicit_animation.dart – 隐式动画示例
  • explicit_animation.dart – 显式动画设置
  • hero_transition.dart – Hero 动画样板
  • staggered_animation.dart – 交错动画模板

📄 原始文档

完整文档(英文):

https://skills.sh/madteacher/mad-agents-skills/flutter-animations

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。