AnimationTimeline.breath constructor

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

Builds a synchronized multi-controller pulse.

The generated sequence is:

  1. animate all controllers forward together
  2. optional hold
  3. animate all controllers reverse together
  4. optional rest

This is useful for breathing cards, modal emphasis, and coordinated spotlight effects that should move as one unit instead of staging a sweep.

Implementation

factory AnimationTimeline.breath({
  required List<AnimationController> controllers,
  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>[
    AnimationTimelineStep.parallel(
      [
        for (var index = 0; index < controllers.length; index++)
          AnimationTimelineStep.forward(
            controllers[index],
            label: labelBuilder?.call(index, 'in') ?? 'breath-in-$index',
          ),
      ],
      label:
          labelBuilder?.call(controllers.length - 1, 'expand') ??
          'breath-expand',
    ),
    if (hold > Duration.zero)
      AnimationTimelineStep.delay(
        hold,
        label:
            labelBuilder?.call(controllers.length - 1, 'hold') ??
            'breath-hold',
      ),
    AnimationTimelineStep.parallel(
      [
        for (var index = 0; index < controllers.length; index++)
          AnimationTimelineStep.reverse(
            controllers[index],
            label: labelBuilder?.call(index, 'out') ?? 'breath-out-$index',
          ),
      ],
      label:
          labelBuilder?.call(controllers.length - 1, 'contract') ??
          'breath-contract',
    ),
    if (rest > Duration.zero)
      AnimationTimelineStep.delay(
        rest,
        label:
            labelBuilder?.call(controllers.length - 1, 'rest') ??
            'breath-rest',
      ),
  ];

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