AnimationTimeline.fan constructor

AnimationTimeline.fan({
  1. required List<AnimationController> controllers,
  2. Duration gap = Duration.zero,
  3. Duration hold = Duration.zero,
  4. Duration rest = Duration.zero,
  5. bool repeat = false,
  6. Object? id,
  7. String labelBuilder(
    1. int index,
    2. String phase
    )?,
  8. void onStepStart(
    1. int index,
    2. AnimationTimelineStep step,
    3. TimelineDirection direction
    )?,
  9. void onStepComplete(
    1. int index,
    2. AnimationTimelineStep step,
    3. TimelineDirection direction
    )?,
})

Builds a fan choreography with staggered entrance and synchronized exit.

The generated sequence is:

  1. animate each controller forward in index order with optional gap
  2. optional hold
  3. animate all controllers reverse together
  4. optional rest

This is useful for menu reveals, panel blooms, and stepped entrances that should collapse back out in one coordinated beat.

Implementation

factory AnimationTimeline.fan({
  required List<AnimationController> controllers,
  Duration gap = Duration.zero,
  Duration hold = Duration.zero,
  Duration rest = Duration.zero,
  bool repeat = false,
  Object? id,
  String Function(int index, String phase)? labelBuilder,
  void Function(
    int index,
    AnimationTimelineStep step,
    TimelineDirection direction,
  )?
  onStepStart,
  void Function(
    int index,
    AnimationTimelineStep step,
    TimelineDirection direction,
  )?
  onStepComplete,
}) {
  if (controllers.isEmpty) {
    throw ArgumentError.value(
      controllers,
      'controllers',
      'must not be empty',
    );
  }

  final steps = <AnimationTimelineStep>[];
  for (var index = 0; index < controllers.length; index++) {
    if (index > 0 && gap > Duration.zero) {
      steps.add(
        AnimationTimelineStep.delay(
          gap,
          label: labelBuilder?.call(index, 'gap') ?? 'fan-gap-$index',
        ),
      );
    }
    steps.add(
      AnimationTimelineStep.forward(
        controllers[index],
        label: labelBuilder?.call(index, 'in') ?? 'fan-in-$index',
      ),
    );
  }
  if (hold > Duration.zero) {
    steps.add(
      AnimationTimelineStep.delay(
        hold,
        label:
            labelBuilder?.call(controllers.length - 1, 'hold') ?? 'fan-hold',
      ),
    );
  }
  steps.add(
    AnimationTimelineStep.parallel(
      [
        for (var index = 0; index < controllers.length; index++)
          AnimationTimelineStep.reverse(
            controllers[index],
            label: labelBuilder?.call(index, 'out') ?? 'fan-out-$index',
          ),
      ],
      label:
          labelBuilder?.call(controllers.length - 1, 'collapse') ??
          'fan-collapse',
    ),
  );
  if (rest > Duration.zero) {
    steps.add(
      AnimationTimelineStep.delay(
        rest,
        label:
            labelBuilder?.call(controllers.length - 1, 'rest') ?? 'fan-rest',
      ),
    );
  }

  return AnimationTimeline(
    steps: steps,
    repeat: repeat,
    id: id,
    onStepStart: onStepStart,
    onStepComplete: onStepComplete,
  );
}