🦋 Flutter 动画系统深度解析

Animation · Tween · Curve · Controller 核心架构

源码级别解析 · Flutter SDK animation 包
2026-03-05 | 每日技术深度解读

📋 目录

  • 第一部分:动画基础架构
  • Animation 抽象类与 AnimationStatus 状态机
  • Listener 机制与通知系统
  • 第二部分:AnimationController
  • 核心控制器设计与 Ticker 驱动
  • Simulation 物理模拟系统
  • 第三部分:Tween 插值系统
  • Animatable 抽象与 Tween 实现
  • 各种专用 Tween 类型
  • 第四部分:Curve 缓动曲线
  • ParametricCurve 与 Curve 体系
  • Cubic 贝塞尔曲线与 CatmullRom 样条
  • 第五部分:高级动画组合
  • TweenSequence 序列动画
  • 动画链与组合模式

🏛️ Flutter 动画系统架构

Flutter 动画系统采用分层架构设计,每一层都有明确的职责边界:

Animation
值容器 + 状态管理
AnimationController
时间控制 + 帧驱动
Tween
值插值 + 类型转换
Curve
缓动函数 + 时序变换

设计理念:动画系统采用组合优于继承的原则,通过 Animation + Tween + Curve 的组合实现灵活的动画效果。

🎯 Animation 抽象类

Animation<T> 是整个动画系统的核心抽象,它代表一个可能随时间变化的值

abstract class Animation<T> extends Listenable 
    implements ValueListenable<T> {
  const Animation();
  
  // 核心属性
  T get value;                    // 当前值
  AnimationStatus get status;     // 当前状态
  
  // 状态判断
  bool get isDismissed => status.isDismissed;
  bool get isCompleted => status.isCompleted;
  bool get isAnimating => status.isAnimating;
  
  // 监听器管理
  void addListener(VoidCallback listener);
  void addStatusListener(AnimationStatusListener listener);
  
  // 链式调用
  Animation<U> drive<U>(Animatable<U> child);
}

📊 AnimationStatus 状态机

AnimationStatus 定义了动画的四种状态:

enum AnimationStatus {
  dismissed,  // 停止在起点 (value = 0.0)
  forward,    // 正向运行 (0.0 → 1.0)
  reverse,    // 反向运行 (1.0 → 0.0)
  completed;  // 停止在终点 (value = 1.0)
}
状态含义valueisAnimating
dismissed停止在起点0.0false
forward正向播放0.0→1.0true
reverse反向播放1.0→0.0true
completed停止在终点1.0false

🔄 状态转换图

              ┌─────────────────────────────────────────┐
              │                                         │
              ▼                                         │
        ┌──────────┐      forward()       ┌──────────┐ │
        │ dismissed│──────────────────────▶│ forward  │ │
        └──────────┘                       └──────────┘ │
              ▲                                  │       │
              │                                  │       │
              │ reverse()                        │       │
              │                                  ▼       │
        ┌──────────┐                     ┌──────────┐  │
        │completed │◀────────────────────│completed │──┘
        └──────────┘  (到达终点时)        └──────────┘

状态转换触发条件:

  • forward() → dismissed/任意 → forward
  • reverse() → completed/任意 → reverse
  • 动画完成 → forward → completed / reverse → dismissed

👂 监听器系统

Animation 提供两种监听器类型:

值监听器

animation.addListener(() {
  print('当前值: ${animation.value}');
  setState(() {});
});

状态监听器

animation.addStatusListener((status) {
  if (status == AnimationStatus.completed)
    animation.reverse();
  if (status == AnimationStatus.dismissed)
    animation.forward();
});

🧩 Listener Helpers Mixin

Flutter 提供三个 Mixin 来简化监听器管理:

// 懒加载监听器 Mixin - 有监听器时才开始工作
mixin AnimationLazyListenerMixin {
  int _listenerCounter = 0;
  
  void didRegisterListener() {
    if (_listenerCounter == 0) 
      didStartListening();
    _listenerCounter += 1;
  }
  
  void didUnregisterListener() {
    _listenerCounter -= 1;
    if (_listenerCounter == 0) 
      didStopListening();
  }
  
  bool get isListening => _listenerCounter > 0;
}

📝 AnimationLocalListenersMixin

实现 addListener/removeListener 协议:

mixin AnimationLocalListenersMixin {
  final _listeners = HashedObserverList<VoidCallback>();
  
  void addListener(VoidCallback listener) {
    didRegisterListener();
    _listeners.add(listener);
  }
  
  void removeListener(VoidCallback listener) {
    if (_listeners.remove(listener))
      didUnregisterListener();
  }
  
  void notifyListeners() {
    for (final listener in _listeners.toList())
      if (_listeners.contains(listener)) listener();
  }
}

📝 AnimationLocalStatusListenersMixin

mixin AnimationLocalStatusListenersMixin {
  final _statusListeners = ObserverList<AnimationStatusListener>();
  
  void addStatusListener(AnimationStatusListener listener) {
    didRegisterListener();
    _statusListeners.add(listener);
  }
  
  void removeStatusListener(AnimationStatusListener listener) {
    if (_statusListeners.remove(listener))
      didUnregisterListener();
  }
  
  void notifyStatusListeners(AnimationStatus status) {
    for (final listener in _statusListeners.toList())
      if (_statusListeners.contains(listener)) 
        listener(status);
  }
}

🎮 AnimationController 概览

AnimationController 是动画系统的核心控制器:

  • 继承自 Animation<double>
  • 使用 Ticker 驱动帧更新
  • 管理动画的时间进度(默认 0.0 → 1.0)
  • 提供 forward/reverse/stop/repeat/fling 等控制方法
  • 支持物理模拟(Spring Simulation)

关键特性:AnimationController 是唯一一个"主动"产生值的 Animation,其他 Animation 都是被动响应值变化。

🏗️ AnimationController 构造函数

class AnimationController extends Animation<double>
    with AnimationEagerListenerMixin,
         AnimationLocalListenersMixin,
         AnimationLocalStatusListenersMixin {
  
  AnimationController({
    double? value,              // 初始值
    this.duration,              // 正向动画时长
    this.reverseDuration,       // 反向动画时长
    this.lowerBound = 0.0,      // 下界
    this.upperBound = 1.0,      // 上界
    required TickerProvider vsync,  // 必需
  }) {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }
  
  // 无界构造函数(用于物理模拟)
  AnimationController.unbounded({
    double value = 0.0,
    required TickerProvider vsync,
  }) : lowerBound = double.negativeInfinity,
       upperBound = double.infinity;
}

⏱️ Ticker 与 vsync

Ticker 是帧回调的封装,每帧调用一次:

// 典型用法:State 实现 TickerProvider
class _MyWidgetState extends State<MyWidget> 
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,  // Mixin 提供了 TickerProvider
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

🔄 _tick 核心方法

每帧调用,驱动动画进度更新:

void _tick(Duration elapsed) {
  _lastElapsedDuration = elapsed;
  
  final double elapsedInSeconds = 
      elapsed.inMicroseconds.toDouble() / 
      Duration.microsecondsPerSecond;
  
  // 通过 Simulation 计算当前值
  _value = clampDouble(
    _simulation!.x(elapsedInSeconds), 
    lowerBound, upperBound
  );
  
  // 检查动画是否完成
  if (_simulation!.isDone(elapsedInSeconds)) {
    _status = (_direction == _AnimationDirection.forward)
        ? AnimationStatus.completed
        : AnimationStatus.dismissed;
    stop(canceled: false);
  }
  
  notifyListeners();      // 通知值监听器
  _checkStatusChanged();  // 通知状态监听器
}

➡️ 动画方向控制

enum _AnimationDirection { forward, reverse }

// forward 方法
TickerFuture forward({double? from}) {
  _direction = _AnimationDirection.forward;
  if (from != null) value = from;
  return _animateToInternal(upperBound);
}

// reverse 方法
TickerFuture reverse({double? from}) {
  _direction = _AnimationDirection.reverse;
  if (from != null) value = from;
  return _animateToInternal(lowerBound);
}

// toggle 方法
TickerFuture toggle({double? from}) {
  _direction = isForwardOrCompleted 
      ? _AnimationDirection.reverse 
      : _AnimationDirection.forward;
  return _animateToInternal(
    _direction == forward ? upperBound : lowerBound
  );
}

🎯 animateTo 方法

驱动动画到指定目标值:

TickerFuture animateTo(
  double target, 
  {Duration? duration, Curve curve = Curves.linear}
) {
  _direction = _AnimationDirection.forward;
  return _animateToInternal(target, 
    duration: duration, curve: curve);
}

// 内部实现
TickerFuture _animateToInternal(double target, ...) {
  // 计算动画时长
  var simulationDuration = duration;
  if (simulationDuration == null) {
    final double range = upperBound - lowerBound;
    final double remainingFraction = 
        (target - _value).abs() / range;
    simulationDuration = this.duration! * remainingFraction;
  }
  
  return _startSimulation(
    _InterpolationSimulation(_value, target, 
      simulationDuration, curve, scale)
  );
}

📊 _InterpolationSimulation

class _InterpolationSimulation extends Simulation {
  _InterpolationSimulation(
    this._begin, this._end, 
    Duration duration, this._curve, double scale
  ) : _durationInSeconds = 
      (duration.inMicroseconds * scale) / 
      Duration.microsecondsPerSecond;

  @override
  double x(double timeInSeconds) {
    final double t = clampDouble(
      timeInSeconds / _durationInSeconds, 0.0, 1.0);
    return switch (t) {
      0.0 => _begin,
      1.0 => _end,
      _   => _begin + (_end - _begin) * _curve.transform(t),
    };
  }

  @override
  bool isDone(double timeInSeconds) => 
    timeInSeconds > _durationInSeconds;
}

🔁 repeat 方法

TickerFuture repeat({
  double? min,
  double? max,
  bool reverse = false,  // 往返(乒乓效果)
  Duration? period,
  int? count,            // null = 无限循环
}) {
  min ??= lowerBound;
  max ??= upperBound;
  period ??= duration;
  
  stop();
  return _startSimulation(
    _RepeatingSimulation(_value, min, max, 
      reverse, period!, _directionSetter, count),
  );
}

// 使用示例
controller.repeat(reverse: true);  // 往返无限循环
controller.repeat(count: 3);       // 重复 3 次

🔄 _RepeatingSimulation

class _RepeatingSimulation extends Simulation {
  @override
  double x(double timeInSeconds) {
    final double totalTimeInSeconds = timeInSeconds + _initialT;
    final double t = (totalTimeInSeconds / _periodInSeconds) % 1.0;
    final bool isPlayingReverse = 
        (totalTimeInSeconds ~/ _periodInSeconds).isOdd;

    if (reverse && isPlayingReverse) {
      directionSetter(_AnimationDirection.reverse);
      return ui.lerpDouble(max, min, t)!;
    } else {
      directionSetter(_AnimationDirection.forward);
      return ui.lerpDouble(min, max, t)!;
    }
  }

  @override
  bool isDone(double timeInSeconds) {
    return count != null && 
           (timeInSeconds >= _exitTimeInSeconds);
  }
}

🚀 fling 方法

基于弹簧物理的甩动动画:

TickerFuture fling({
  double velocity = 1.0,
  SpringDescription? springDescription,
}) {
  springDescription ??= _kFlingSpringDescription;
  _direction = velocity < 0.0 
      ? _AnimationDirection.reverse 
      : _AnimationDirection.forward;
  
  final double target = velocity < 0.0
      ? lowerBound - _kFlingTolerance.distance
      : upperBound + _kFlingTolerance.distance;
  
  final simulation = SpringSimulation(
    springDescription, value, target, velocity * scale
  )..tolerance = _kFlingTolerance;
  
  stop();
  return _startSimulation(simulation);
}

⚙️ AnimationBehavior

控制无障碍模式下动画的行为:

enum AnimationBehavior {
  normal,   // 禁用动画时缩短时长
  preserve, // 不受影响
}

// 在 _animateToInternal 中的应用
final double scale = switch (animationBehavior) {
  AnimationBehavior.normal 
    when SemanticsBinding.instance.disableAnimations => 0.05,
  AnimationBehavior.normal || AnimationBehavior.preserve => 1.0,
};

// fling 中速度放大
AnimationBehavior.normal 
  when SemanticsBinding.instance.disableAnimations => 200.0

🔧 Tween 概览

Tween(插值器)用于将 0.0-1.0 的动画值转换为任意类型:

    AnimationController (0.0 → 1.0)
            │
            ▼
    ┌───────────────────┐
    │   Tween<Color>    │  lerp(begin: red, end: blue)
    └───────────────────┘
            │
            ▼
    Animation<Color> (red → blue)

核心概念:Animatable 是可动画值的抽象,Tween 是最常用的实现。

🎯 Animatable 抽象类

abstract class Animatable<T> {
  const Animatable();

  /// 将输入 t (0.0-1.0) 转换为输出值
  T transform(double t);

  /// 对 Animation 的当前值求值
  T evaluate(Animation<double> animation) => 
    transform(animation.value);

  /// 创建一个由 parent 驱动的 Animation
  Animation<T> animate(Animation<double> parent) {
    return _AnimatedEvaluation<T>(parent, this);
  }

  /// 链式组合
  Animatable<T> chain(Animatable<double> parent) {
    return _ChainedEvaluation<T>(parent, this);
  }
}

📐 Tween 类

最常用的 Animatable 实现,执行线性插值:

class Tween<T> extends Animatable<T> {
  Tween({this.begin, this.end});

  T? begin;  // 起始值
  T? end;    // 结束值

  /// 线性插值
  @protected
  T lerp(double t) {
    return (begin as dynamic) + 
           ((end as dynamic) - (begin as dynamic)) * t as T;
  }

  @override
  T transform(double t) {
    if (t == 0.0) return begin as T;
    if (t == 1.0) return end as T;
    return lerp(t);
  }
}

💡 Tween 使用示例

// 基本用法
final animation = Tween<double>(
  begin: 0.0, end: 300.0,
).animate(_controller);

// 链式调用
final alignment = _controller.drive(
  AlignmentTween(
    begin: Alignment.topLeft,
    end: Alignment.topRight,
  ),
);

// 结合 Curve
final alignment3 = _controller
    .drive(CurveTween(curve: Curves.easeIn))
    .drive(AlignmentTween(
      begin: Alignment.topLeft,
      end: Alignment.topRight,
    ));

🎨 专用 Tween 类型

Tween 类型用途lerp 实现
ColorTween颜色插值Color.lerp()
SizeTween尺寸插值Size.lerp()
RectTween矩形插值Rect.lerp()
IntTween整数(四舍五入)round()
StepTween整数(向下取整)floor()
ConstantTween常量值返回固定值
CurveTween曲线变换curve.transform()

🎨 ColorTween

class ColorTween extends Tween<Color?> {
  ColorTween({super.begin, super.end});

  @override
  Color? lerp(double t) => Color.lerp(begin, end, t);
}

// 使用示例
final colorAnimation = ColorTween(
  begin: Colors.red,
  end: Colors.blue,
).animate(_controller);

AnimatedBuilder(
  animation: colorAnimation,
  builder: (context, child) {
    return Container(color: colorAnimation.value);
  },
)

注意:null 值代表"无颜色",与 Colors.transparent 不同。

🔢 IntTween 与 StepTween

IntTween(四舍五入)

class IntTween extends Tween<int> {
  @override
  int lerp(double t) => 
    (begin! + (end! - begin!) * t).round();
}

// t=0.5 时: 0→100 = 50

StepTween(向下取整)

class StepTween extends Tween<int> {
  @override
  int lerp(double t) => 
    (begin! + (end! - begin!) * t).floor();
}

// t=0.99 时: 0→100 = 99

📈 CurveTween

class CurveTween extends Animatable<double> {
  CurveTween({required this.curve});

  Curve curve;

  @override
  double transform(double t) {
    if (t == 0.0 || t == 1.0) return t;
    return curve.transform(t);
  }
}

// 使用示例
final curvedAnimation = _controller.drive(
  CurveTween(curve: Curves.easeInOut),
);

// 或通过 chain
final curvedAnimation2 = Tween<double>(
  begin: 0.0, end: 100.0,
).chain(CurveTween(curve: Curves.easeInOut))
 .animate(_controller);

🔄 ReverseTween

class ReverseTween<T> extends Tween<T> {
  ReverseTween(this.parent) 
      : super(begin: parent.end, end: parent.begin);

  final Tween<T> parent;

  @override
  T lerp(double t) => parent.lerp(1.0 - t);
}

// 使用示例
final forwardTween = Tween<double>(begin: 0.0, end: 100.0);
final reverseTween = ReverseTween<double>(forwardTween);

// reverseTween.transform(0.0) = 100.0
// reverseTween.transform(1.0) = 0.0

🌊 Curve 缓动曲线

Curve 定义动画的时序变换,让动画更自然:

    线性动画 (Linear)         缓动动画 (Curved)
    
    │  /                     │     ╭─────
    │ /                      │    ╱
    │/                       │   ╱
    └──────────>             └──────────>
    
    匀速变化                  先快后慢 / 先慢后快

核心约定:Curve 必须满足 t=0.0 → 0.0,t=1.0 → 1.0。

📐 ParametricCurve

abstract class ParametricCurve<T> {
  const ParametricCurve();

  /// 返回参数 t 对应的曲线值
  T transform(double t) {
    assert(t >= 0.0 && t <= 1.0);
    return transformInternal(t);
  }

  /// 子类实现此方法
  @protected
  T transformInternal(double t) {
    throw UnimplementedError();
  }
}

🎯 Curve 抽象类

@immutable
abstract class Curve extends ParametricCurve<double> {
  const Curve();

  @override
  double transform(double t) {
    // 确保边界值正确
    if (t == 0.0 || t == 1.0) return t;
    return super.transform(t);
  }

  /// 返回反转曲线
  Curve get flipped => FlippedCurve(this);
}

// 线性曲线实现
class _Linear extends Curve {
  const _Linear._();
  @override
  double transformInternal(double t) => t;
}

🌊 Cubic 贝塞尔曲线

class Cubic extends Curve {
  const Cubic(this.a, this.b, this.c, this.d);

  final double a;  // 第一控制点 x
  final double b;  // 第一控制点 y
  final double c;  // 第二控制点 x
  final double d;  // 第二控制点 y

  @override
  double transformInternal(double t) {
    // 二分查找求解
    var start = 0.0, end = 1.0;
    while (true) {
      final midpoint = (start + end) / 2;
      final estimate = _evaluateCubic(a, c, midpoint);
      if ((t - estimate).abs() < 0.001)
        return _evaluateCubic(b, d, midpoint);
      if (estimate < t) start = midpoint;
      else end = midpoint;
    }
  }
}

🧮 Cubic 计算原理

double _evaluateCubic(double a, double b, double m) {
  // 三阶贝塞尔公式
  return 3 * a * (1 - m) * (1 - m) * m + 
         3 * b * (1 - m) * m * m + 
         m * m * m;
}
    Cubic(0.4, 0.0, 0.2, 1.0) - Curves.easeInOutCubic
    
    y │                      ╭────────────
      │                   ╭─╯
    1 │                ╭──╯
      │             ╭──╯
    0 │────────────╯
      └──────────────────────────── x
      0                          1

📚 预定义曲线

曲线效果控制点
Curves.linear线性-
Curves.ease标准缓动Cubic(0.25, 0.1, 0.25, 1.0)
Curves.easeIn慢入Cubic(0.42, 0, 1, 1)
Curves.easeOut慢出Cubic(0, 0, 0.58, 1)
Curves.easeInOut慢入慢出Cubic(0.42, 0, 0.58, 1)
Curves.elasticIn弹性进入振荡曲线
Curves.bounceOut弹跳分段衰减

⏱️ Interval 曲线

在指定时间区间内应用曲线:

class Interval extends Curve {
  const Interval(this.begin, this.end, {this.curve = Curves.linear});

  final double begin;  // 开始时间点 (0.0-1.0)
  final double end;    // 结束时间点 (0.0-1.0)
  final Curve curve;

  @override
  double transformInternal(double t) {
    // 将 t 映射到 [begin, end] 区间
    t = clampDouble((t - begin) / (end - begin), 0.0, 1.0);
    if (t == 0.0 || t == 1.0) return t;
    return curve.transform(t);
  }
}

🎬 Interval 使用示例

// 三个动画在不同时间段执行
final first = Tween<double>(begin: 0.0, end: 1.0)
    .chain(CurveTween(curve: Interval(0.0, 0.33)))
    .animate(_controller);

final second = Tween<double>(begin: 0.0, end: 1.0)
    .chain(CurveTween(curve: Interval(0.33, 0.66)))
    .animate(_controller);

final third = Tween<double>(begin: 0.0, end: 1.0)
    .chain(CurveTween(curve: Interval(0.66, 1.0)))
    .animate(_controller);

// 时间线:
// 0.0 ───── 0.33 ───── 0.66 ───── 1.0
// │  first  │  second  │  third  │

⚡ Threshold 曲线

阶跃曲线,在阈值点瞬间跳变:

class Threshold extends Curve {
  const Threshold(this.threshold);

  final double threshold;

  @override
  double transformInternal(double t) => 
    t < threshold ? 0.0 : 1.0;
}

// 使用示例:在 50% 时瞬间切换
final jumpAnimation = _controller.drive(
  CurveTween(curve: Threshold(0.5)),
);

// 0.0 ─────── 0.5 ─────── 1.0
// │    0.0    │    1.0    │
//             ↑ 瞬间跳变

🔁 SawTooth 曲线

锯齿曲线,重复多次线性增长:

class SawTooth extends Curve {
  const SawTooth(this.count);

  final int count;

  @override
  double transformInternal(double t) {
    t *= count;
    return t - t.truncateToDouble();  // 取小数部分
  }
}

// 重复 3 次的锯齿动画
final sawAnimation = _controller.drive(
  CurveTween(curve: SawTooth(3)),
);

// 0.0 ─── 0.33 ─── 0.66 ─── 1.0
// │  ↗    │  ↗    │  ↗    │

🔄 FlippedCurve

class FlippedCurve extends Curve {
  const FlippedCurve(this.curve);

  final Curve curve;

  @override
  double transformInternal(double t) => 
    1.0 - curve.transform(1.0 - t);
}

// 使用示例
final easeOut = Curves.easeIn.flipped;

// 原曲线                  翻转后
// │      ╭───            │ ───╮
// │     ╱                │    ╲
// │──╯                   │       ╰──

✂️ Split 曲线

在指定点分割,前后使用不同曲线:

class Split extends Curve {
  const Split(this.split, {
    this.beginCurve = Curves.linear,
    this.endCurve = Curves.easeOutCubic,
  });

  final double split;
  final Curve beginCurve;
  final Curve endCurve;

  @override
  double transform(double t) {
    if (t < split) {
      return lerpDouble(0, split, 
        beginCurve.transform(t / split))!;
    } else {
      return lerpDouble(split, 1, 
        endCurve.transform((t - split) / (1 - split)))!;
    }
  }
}

🌐 Curve2D 二维曲线

abstract class Curve2D extends ParametricCurve<Offset> {
  const Curve2D();

  /// 生成采样点
  Iterable<Curve2DSample> generateSamples({
    double start = 0.0,
    double end = 1.0,
    double tolerance = 1e-10,
  });

  /// 返回 x 对应的参数 t
  double findInverse(double x);
}

class Curve2DSample {
  const Curve2DSample(this.t, this.value);
  final double t;
  final Offset value;
}

🔸 CatmullRomSpline

class CatmullRomSpline extends Curve2D {
  CatmullRomSpline(
    List<Offset> controlPoints, {
    double tension = 0.0,
    Offset? startHandle,
    Offset? endHandle,
  });

  @override
  Offset transformInternal(double t) {
    // 计算当前 t 对应的曲线段
    // 使用三次多项式计算点
  }
}

// 使用示例:自定义运动路径
final spline = CatmullRomSpline([
  Offset(0, 0),
  Offset(100, 200),
  Offset(200, 100),
  Offset(300, 300),
]);

📈 CatmullRomCurve

class CatmullRomCurve extends Curve {
  CatmullRomCurve(this.controlPoints, {this.tension = 0.0});

  final List<Offset> controlPoints;
  final double tension;

  static bool validateControlPoints(
    List<Offset>? controlPoints, {
    double tension = 0.0,
    List<String>? reasons,
  }) {
    // 验证:至少 2 个点,X 在 (0,1),单调递增
  }
}

🎭 TweenSequence

class TweenSequence<T> extends Animatable<T> {
  TweenSequence(List<TweenSequenceItem<T>> items) {
    _items.addAll(items);
    
    // 计算总权重
    var totalWeight = 0.0;
    for (final item in _items)
      totalWeight += item.weight;
    
    // 计算每个 item 的时间区间
    var start = 0.0;
    for (var i = 0; i < _items.length; i++) {
      final double end = i == _items.length - 1 
          ? 1.0 
          : start + _items[i].weight / totalWeight;
      _intervals.add(_Interval(start, end));
      start = end;
    }
  }
}

📦 TweenSequenceItem

class TweenSequenceItem<T> {
  const TweenSequenceItem({
    required this.tween,
    required this.weight,
  });

  final Animatable<T> tween;
  final double weight;
}

// 使用示例
final sequence = TweenSequence<double>([
  TweenSequenceItem(
    tween: Tween(begin: 0.0, end: 10.0),
    weight: 0.4,  // 占 40% 时间
  ),
  TweenSequenceItem(
    tween: ConstantTween(10.0),  // 保持 10.0
    weight: 0.2,
  ),
  TweenSequenceItem(
    tween: Tween(begin: 10.0, end: 5.0),
    weight: 0.4,
  ),
]).animate(_controller);

🔄 FlippedTweenSequence

class FlippedTweenSequence extends TweenSequence<double> {
  FlippedTweenSequence(super.items);

  @override
  double transform(double t) => 1 - super.transform(1 - t);
}

// 原序列:0 → 10 → 10 → 5
// 翻转后:5 → 10 → 10 → 0

// 使用场景:反向播放时使用不同的序列

🧩 动画组合模式

Flutter 提供多种组合动画的方式:

链式调用
.chain() / .drive()
序列组合
TweenSequence
时间编排
Interval
并行控制
多个 Animation

✅ 最佳实践

  • 复用 Tween:Tween 是可变的,可以定义为 static final 复用
  • 正确 dispose:在 State.dispose() 中调用 controller.dispose()
  • 使用 vsync:防止屏幕外动画消耗资源
  • 选择合适的 Mixin:
    • SingleTickerProviderStateMixin - 单个 Controller
    • TickerProviderStateMixin - 多个 Controller
  • 避免过度重建:传递 Animation 对象而非 value
  • 使用 AnimatedWidget:简化监听器管理
  • 考虑无障碍:正确设置 AnimationBehavior

🎉 总结

Flutter 动画系统:Animation · Tween · Curve · Controller

  • Animation<T> - 值容器 + 状态管理 + 监听器
  • AnimationController - 时间控制 + Ticker 驱动 + Simulation
  • Tween - 值插值 + 类型转换
  • Curve - 缓动函数 + 时序变换
  • TweenSequence - 序列动画 + 权重组合