AnimationTimeline.ripple constructor
AnimationTimeline.ripple({
- required List<
AnimationController> controllers, - int? originIndex,
- Duration gap = Duration.zero,
- Duration crestHold = Duration.zero,
- Duration returnGap = Duration.zero,
- Duration rest = Duration.zero,
- bool repeat = false,
- Object? id,
- String labelBuilder()?,
- void onStepStart(
- int index,
- AnimationTimelineStep step,
- TimelineDirection direction
- void onStepComplete(
- int index,
- AnimationTimelineStep step,
- TimelineDirection direction
Builds a center-origin ripple across multiple controllers.
Controllers are animated outward from originIndex, alternating toward
lower and higher indices as distance increases. The return sweep reverses
that resolved ripple order.
This is useful for spotlight ripples, centered selection emphasis, and radial-feeling text/UI motion in a linear controller list.
Implementation
factory AnimationTimeline.ripple({
required List<AnimationController> controllers,
int? originIndex,
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 resolvedOrigin = originIndex ?? ((controllers.length - 1) ~/ 2);
if (resolvedOrigin < 0 || resolvedOrigin >= controllers.length) {
throw RangeError.range(
resolvedOrigin,
0,
controllers.length - 1,
'originIndex',
);
}
final order = <int>[resolvedOrigin];
for (var distance = 1; order.length < controllers.length; distance++) {
final lower = resolvedOrigin - distance;
final upper = resolvedOrigin + distance;
if (lower >= 0) {
order.add(lower);
}
if (upper < controllers.length) {
order.add(upper);
}
}
final steps = <AnimationTimelineStep>[];
for (var position = 0; position < order.length; position++) {
final index = order[position];
if (position > 0 && gap > Duration.zero) {
steps.add(
AnimationTimelineStep.delay(
gap,
label: labelBuilder?.call(index, 'gap') ?? 'ripple-gap-$position',
),
);
}
steps.add(
AnimationTimelineStep.forward(
controllers[index],
label: labelBuilder?.call(index, 'in') ?? 'ripple-in-$index',
),
);
}
if (crestHold > Duration.zero) {
steps.add(
AnimationTimelineStep.delay(
crestHold,
label:
labelBuilder?.call(resolvedOrigin, 'crest-hold') ??
'ripple-crest-hold',
),
);
}
for (var position = order.length - 1; position >= 0; position--) {
final index = order[position];
if (position < order.length - 1 && returnGap > Duration.zero) {
steps.add(
AnimationTimelineStep.delay(
returnGap,
label:
labelBuilder?.call(index, 'return-gap') ??
'ripple-return-gap-$position',
),
);
}
steps.add(
AnimationTimelineStep.reverse(
controllers[index],
label: labelBuilder?.call(index, 'out') ?? 'ripple-out-$index',
),
);
}
if (rest > Duration.zero) {
steps.add(
AnimationTimelineStep.delay(
rest,
label: labelBuilder?.call(resolvedOrigin, 'rest') ?? 'ripple-rest',
),
);
}
return AnimationTimeline(
steps: steps,
repeat: repeat,
id: id,
onStepStart: onStepStart,
onStepComplete: onStepComplete,
);
}