AnimationTimeline.wave constructor

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

Builds a traveling wave across multiple controllers.

The generated sequence is:

  1. animate each controller forward in index order with optional gap
  2. optional crestHold
  3. animate each controller reverse in reverse index order with optional returnGap
  4. optional rest

This is useful for marquee highlights, stepped focus sweeps, and left-to-right then right-to-left motion without manually spelling out the mirrored controller choreography.

Implementation

factory AnimationTimeline.wave({
  required List<AnimationController> controllers,
  Duration gap = Duration.zero,
  Duration crestHold = Duration.zero,
  Duration returnGap = 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') ?? 'wave-gap-$index',
        ),
      );
    }
    steps.add(
      AnimationTimelineStep.forward(
        controllers[index],
        label: labelBuilder?.call(index, 'crest-in') ?? 'wave-in-$index',
      ),
    );
  }
  if (crestHold > Duration.zero) {
    steps.add(
      AnimationTimelineStep.delay(
        crestHold,
        label:
            labelBuilder?.call(controllers.length - 1, 'crest-hold') ??
            'wave-crest-hold',
      ),
    );
  }
  for (var offset = 0; offset < controllers.length; offset++) {
    final index = controllers.length - 1 - offset;
    if (offset > 0 && returnGap > Duration.zero) {
      steps.add(
        AnimationTimelineStep.delay(
          returnGap,
          label:
              labelBuilder?.call(index, 'return-gap') ??
              'wave-return-gap-$index',
        ),
      );
    }
    steps.add(
      AnimationTimelineStep.reverse(
        controllers[index],
        label: labelBuilder?.call(index, 'crest-out') ?? 'wave-out-$index',
      ),
    );
  }
  if (rest > Duration.zero) {
    steps.add(
      AnimationTimelineStep.delay(
        rest,
        label: labelBuilder?.call(0, 'rest') ?? 'wave-rest',
      ),
    );
  }

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