flutter-animation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter Animations Implementation

Flutter动画实现

Goal

目标

Implements and manages Flutter animations, selecting the appropriate animation strategy (implicit, explicit, tween, physics, hero, or staggered) based on UI requirements. Assumes a working Flutter environment, stateful/stateless widget competence, and a standard widget tree structure.
实现并管理Flutter动画,根据UI需求选择合适的动画策略(隐式、显式、补间、物理、Hero或交错动画)。假设已具备可用的Flutter环境、有状态/无状态组件开发能力,以及标准的组件树结构。

Instructions

操作步骤

1. Determine Animation Strategy (Decision Logic)

1. 确定动画策略(决策逻辑)

Evaluate the UI requirement using the following decision tree to select the correct animation approach:
  1. Is the animation a simple property change (color, size, alignment) on a single widget?
    • YES: Use Implicit Animations (e.g.,
      AnimatedContainer
      ).
    • NO: Proceed to 2.
  2. Does the animation model real-world movement (springs, gravity, velocity)?
    • YES: Use Physics-based animation (
      SpringSimulation
      ,
      animateWith
      ).
    • NO: Proceed to 3.
  3. Does the animation involve a widget flying between two different screens/routes?
    • YES: Use Hero Animations (
      Hero
      widget).
    • NO: Proceed to 4.
  4. Does the animation involve multiple sequential or overlapping movements?
    • YES: Use Staggered Animations (Single
      AnimationController
      with multiple
      Tween
      s and
      Interval
      curves).
    • NO: Use Standard Explicit Animations (
      AnimationController
      ,
      Tween
      ,
      AnimatedBuilder
      /
      AnimatedWidget
      ).
STOP AND ASK THE USER: If the requirement is ambiguous, pause and ask the user to clarify the desired visual effect before writing implementation code.
使用以下决策树评估UI需求,选择正确的动画方案:
  1. 动画是否为单个组件的简单属性变化(颜色、尺寸、对齐方式)?
    • 是:使用隐式动画(例如
      AnimatedContainer
      )。
    • 否:进入步骤2。
  2. 动画是否模拟现实世界的运动(弹簧、重力、速度)?
    • 是:使用物理驱动动画
      SpringSimulation
      animateWith
      )。
    • 否:进入步骤3。
  3. 动画是否涉及组件在两个不同页面/路由间的过渡飞行?
    • 是:使用Hero动画
      Hero
      组件)。
    • 否:进入步骤4。
  4. 动画是否包含多个连续或重叠的运动?
    • 是:使用交错动画(单个
      AnimationController
      搭配多个
      Tween
      Interval
      曲线)。
    • 否:使用标准显式动画
      AnimationController
      Tween
      AnimatedBuilder
      /
      AnimatedWidget
      )。
停止并询问用户: 如果需求不明确,请暂停并让用户澄清期望的视觉效果,再编写实现代码。

2. Implement Implicit Animations

2. 实现隐式动画

For simple transitions between values, use implicit animation widgets. Do not manually manage state or controllers.
dart
AnimatedContainer(
  duration: const Duration(milliseconds: 500),
  curve: Curves.bounceIn,
  width: _isExpanded ? 200.0 : 100.0,
  height: _isExpanded ? 200.0 : 100.0,
  decoration: BoxDecoration(
    color: _isExpanded ? Colors.green : Colors.blue,
    borderRadius: BorderRadius.circular(_isExpanded ? 50.0 : 8.0),
  ),
  child: const FlutterLogo(),
)
对于值之间的简单过渡,使用隐式动画组件,无需手动管理状态或控制器。
dart
AnimatedContainer(
  duration: const Duration(milliseconds: 500),
  curve: Curves.bounceIn,
  width: _isExpanded ? 200.0 : 100.0,
  height: _isExpanded ? 200.0 : 100.0,
  decoration: BoxDecoration(
    color: _isExpanded ? Colors.green : Colors.blue,
    borderRadius: BorderRadius.circular(_isExpanded ? 50.0 : 8.0),
  ),
  child: const FlutterLogo(),
)

3. Implement Explicit Animations (Tween-based)

3. 实现显式动画(基于补间)

When you need to control the animation (play, pause, reverse), use an
AnimationController
with a
Tween
. Separate the transition rendering from the state using
AnimatedBuilder
.
dart
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    
    _animation = Tween<double>(begin: 0, end: 300).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    )..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
      
    _controller.forward();
  }

  
  void dispose() {
    _controller.dispose(); // STRICT REQUIREMENT
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return SizedBox(
          height: _animation.value,
          width: _animation.value,
          child: child,
        );
      },
      child: const FlutterLogo(), // Passed as child for performance
    );
  }
}
当需要控制动画(播放、暂停、倒放)时,使用
AnimationController
搭配
Tween
。使用
AnimatedBuilder
将过渡渲染与状态分离。
dart
class _MyAnimatedWidgetState extends State<MyAnimatedWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    
    _animation = Tween<double>(begin: 0, end: 300).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    )..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
      
    _controller.forward();
  }

  
  void dispose() {
    _controller.dispose(); // 严格要求
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return SizedBox(
          height: _animation.value,
          width: _animation.value,
          child: child,
        );
      },
      child: const FlutterLogo(), // 作为子组件传递以提升性能
    );
  }
}

4. Implement Page Route Transitions

4. 实现页面路由过渡

To animate transitions between routes, use
PageRouteBuilder
and chain a
CurveTween
with a
Tween<Offset>
.
dart
Route<void> _createRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DestinationPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(0.0, 1.0);
      const end = Offset.zero;
      const curve = Curves.ease;

      final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
  );
}
要为路由间的过渡添加动画,使用
PageRouteBuilder
并将
CurveTween
Tween<Offset>
链式结合。
dart
Route<void> _createRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DestinationPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(0.0, 1.0);
      const end = Offset.zero;
      const curve = Curves.ease;

      final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
  );
}

5. Implement Physics-Based Animations

5. 实现物理驱动动画

For realistic motion (e.g., snapping back after a drag), calculate velocity and apply a
SpringSimulation
.
dart
void _runSpringAnimation(Offset pixelsPerSecond, Size size, Alignment dragAlignment) {
  _animation = _controller.drive(
    AlignmentTween(begin: dragAlignment, end: Alignment.center),
  );

  final unitsPerSecondX = pixelsPerSecond.dx / size.width;
  final unitsPerSecondY = pixelsPerSecond.dy / size.height;
  final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
  final unitVelocity = unitsPerSecond.distance;

  const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);
  final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);

  _controller.animateWith(simulation);
}
对于逼真的运动效果(例如拖拽后弹回),计算速度并应用
SpringSimulation
dart
void _runSpringAnimation(Offset pixelsPerSecond, Size size, Alignment dragAlignment) {
  _animation = _controller.drive(
    AlignmentTween(begin: dragAlignment, end: Alignment.center),
  );

  final unitsPerSecondX = pixelsPerSecond.dx / size.width;
  final unitsPerSecondY = pixelsPerSecond.dy / size.height;
  final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
  final unitVelocity = unitsPerSecond.distance;

  const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);
  final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);

  _controller.animateWith(simulation);
}

6. Implement Hero Animations (Shared Element)

6. 实现Hero动画(共享元素)

To fly a widget between routes, wrap the identical widget tree in both routes with a
Hero
widget using the exact same
tag
.
dart
// Source Route
Hero(
  tag: 'unique-photo-tag',
  child: Image.asset('photo.png', width: 100),
)

// Destination Route
Hero(
  tag: 'unique-photo-tag',
  child: Image.asset('photo.png', width: 300),
)
要让组件在路由间过渡飞行,在两个路由中使用完全相同的
tag
,将相同的组件树包裹在
Hero
组件中。
dart
// 源路由
Hero(
  tag: 'unique-photo-tag',
  child: Image.asset('photo.png', width: 100),
)

// 目标路由
Hero(
  tag: 'unique-photo-tag',
  child: Image.asset('photo.png', width: 300),
)

7. Implement Staggered Animations

7. 实现交错动画

For sequential or overlapping animations, use a single
AnimationController
and define multiple
Tween
s with
Interval
curves.
dart
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 build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Opacity(
          opacity: opacity.value,
          child: Container(width: width.value, height: 50, color: Colors.blue),
        );
      },
    );
  }
}
对于连续或重叠的动画,使用单个
AnimationController
并为多个
Tween
定义
Interval
曲线。
dart
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 build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Opacity(
          opacity: opacity.value,
          child: Container(width: width.value, height: 50, color: Colors.blue),
        );
      },
    );
  }
}

8. Validate-and-Fix Loop

8. 验证与修复循环

After generating animation code, verify the following:
  1. Does the
    State
    class use
    SingleTickerProviderStateMixin
    (or
    TickerProviderStateMixin
    for multiple controllers)?
  2. Is
    _controller.dispose()
    explicitly called in the
    dispose()
    method?
  3. If using
    AnimatedBuilder
    , is the static widget passed to the
    child
    parameter rather than rebuilt inside the
    builder
    function? If any of these are missing, fix the code immediately before presenting it to the user.
生成动画代码后,验证以下内容:
  1. State
    类是否使用了
    SingleTickerProviderStateMixin
    (多控制器时使用
    TickerProviderStateMixin
    )?
  2. 是否在
    dispose()
    方法中显式调用了
    _controller.dispose()
  3. 如果使用
    AnimatedBuilder
    ,静态组件是否传递给了
    child
    参数,而非在
    builder
    函数内重建? 如果以上任何一项缺失,在将代码呈现给用户前立即修复。

Constraints

约束条件

  • Strict Disposal: You MUST include
    dispose()
    methods for all
    AnimationController
    instances to prevent memory leaks.
  • No URLs: Do not include external links or URLs in the output or comments.
  • Immutability: Treat
    Tween
    and
    Curve
    classes as stateless and immutable. Do not attempt to mutate them after instantiation.
  • Performance: Always use
    AnimatedBuilder
    or
    AnimatedWidget
    instead of calling
    setState()
    inside a controller's
    addListener
    when building complex widget trees.
  • Hero Tags: Hero tags MUST be identical and unique per route transition. Do not use generic tags like
    'image'
    if multiple heroes exist.
  • 严格销毁: 必须为所有
    AnimationController
    实例添加
    dispose()
    方法,以防止内存泄漏。
  • 禁止URL: 输出或注释中不得包含外部链接或URL。
  • 不可变性:
    Tween
    Curve
    类视为无状态且不可变的,实例化后不要尝试修改它们。
  • 性能优化: 构建复杂组件树时,始终使用
    AnimatedBuilder
    AnimatedWidget
    ,而非在控制器的
    addListener
    中调用
    setState()
  • Hero标签: Hero标签必须完全相同,且每个路由过渡的标签唯一。如果存在多个Hero组件,不要使用类似
    'image'
    的通用标签。