Skip to content

Commit 2bb19a9

Browse files
authored
Added onEnd callback into AnimatedSize (#139859)
close #106439
1 parent 5a5683d commit 2bb19a9

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

packages/flutter/lib/src/rendering/animated_size.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
8282
super.textDirection,
8383
super.child,
8484
Clip clipBehavior = Clip.hardEdge,
85+
VoidCallback? onEnd,
8586
}) : _vsync = vsync,
8687
_clipBehavior = clipBehavior {
8788
_controller = AnimationController(
@@ -97,6 +98,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
9798
parent: _controller,
9899
curve: curve,
99100
);
101+
_onEnd = onEnd;
100102
}
101103

102104
/// When asserts are enabled, returns the animation controller that is used
@@ -203,6 +205,19 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
203205
_controller.resync(vsync);
204206
}
205207

208+
/// Called every time an animation completes.
209+
///
210+
/// This can be useful to trigger additional actions (e.g. another animation)
211+
/// at the end of the current animation.
212+
VoidCallback? get onEnd => _onEnd;
213+
VoidCallback? _onEnd;
214+
set onEnd(VoidCallback? value) {
215+
if (value == _onEnd) {
216+
return;
217+
}
218+
_onEnd = value;
219+
}
220+
206221
@override
207222
void attach(PipelineOwner owner) {
208223
super.attach(owner);
@@ -216,11 +231,13 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
216231
// already, to resume interrupted resizing animation.
217232
markNeedsLayout();
218233
}
234+
_controller.addStatusListener(_animationStatusListener);
219235
}
220236

221237
@override
222238
void detach() {
223239
_controller.stop();
240+
_controller.removeStatusListener(_animationStatusListener);
224241
super.detach();
225242
}
226243

@@ -363,6 +380,16 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
363380
}
364381
}
365382

383+
void _animationStatusListener(AnimationStatus status) {
384+
switch (status) {
385+
case AnimationStatus.completed:
386+
_onEnd?.call();
387+
case AnimationStatus.dismissed:
388+
case AnimationStatus.forward:
389+
case AnimationStatus.reverse:
390+
}
391+
}
392+
366393
@override
367394
void paint(PaintingContext context, Offset offset) {
368395
if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) {

packages/flutter/lib/src/widgets/animated_size.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class AnimatedSize extends StatefulWidget {
3131
required this.duration,
3232
this.reverseDuration,
3333
this.clipBehavior = Clip.hardEdge,
34+
this.onEnd,
3435
});
3536

3637
/// The widget below this widget in the tree.
@@ -78,6 +79,12 @@ class AnimatedSize extends StatefulWidget {
7879
/// Defaults to [Clip.hardEdge].
7980
final Clip clipBehavior;
8081

82+
/// Called every time an animation completes.
83+
///
84+
/// This can be useful to trigger additional actions (e.g. another animation)
85+
/// at the end of the current animation.
86+
final VoidCallback? onEnd;
87+
8188
@override
8289
State<AnimatedSize> createState() => _AnimatedSizeState();
8390
}
@@ -93,6 +100,7 @@ class _AnimatedSizeState
93100
reverseDuration: widget.reverseDuration,
94101
vsync: this,
95102
clipBehavior: widget.clipBehavior,
103+
onEnd: widget.onEnd,
96104
child: widget.child,
97105
);
98106
}
@@ -107,6 +115,7 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
107115
this.reverseDuration,
108116
required this.vsync,
109117
this.clipBehavior = Clip.hardEdge,
118+
this.onEnd,
110119
});
111120

112121
final AlignmentGeometry alignment;
@@ -119,6 +128,8 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
119128

120129
final Clip clipBehavior;
121130

131+
final VoidCallback? onEnd;
132+
122133
@override
123134
RenderAnimatedSize createRenderObject(BuildContext context) {
124135
return RenderAnimatedSize(
@@ -129,6 +140,7 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
129140
vsync: vsync,
130141
textDirection: Directionality.maybeOf(context),
131142
clipBehavior: clipBehavior,
143+
onEnd: onEnd,
132144
);
133145
}
134146

@@ -141,7 +153,8 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
141153
..curve = curve
142154
..vsync = vsync
143155
..textDirection = Directionality.maybeOf(context)
144-
..clipBehavior = clipBehavior;
156+
..clipBehavior = clipBehavior
157+
..onEnd = onEnd;
145158
}
146159

147160
@override

packages/flutter/test/widgets/animated_size_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,61 @@ void main() {
8787
expect(box.size.height, equals(100.0));
8888
});
8989

90+
testWidgets('calls onEnd when animation is completed', (WidgetTester tester) async {
91+
int callCount = 0;
92+
void handleEnd() {
93+
callCount++;
94+
}
95+
96+
await tester.pumpWidget(
97+
Center(
98+
child: AnimatedSize(
99+
onEnd: handleEnd,
100+
duration: const Duration(milliseconds: 200),
101+
child: const SizedBox(
102+
width: 100.0,
103+
height: 100.0,
104+
),
105+
),
106+
),
107+
);
108+
109+
expect(callCount, equals(0));
110+
111+
await tester.pumpWidget(
112+
Center(
113+
child: AnimatedSize(
114+
onEnd: handleEnd,
115+
duration: const Duration(milliseconds: 200),
116+
child: const SizedBox(
117+
width: 200.0,
118+
height: 200.0,
119+
),
120+
),
121+
),
122+
);
123+
124+
expect(callCount, equals(0));
125+
await tester.pumpAndSettle();
126+
expect(callCount, equals(1));
127+
128+
await tester.pumpWidget(
129+
Center(
130+
child: AnimatedSize(
131+
onEnd: handleEnd,
132+
duration: const Duration(milliseconds: 200),
133+
child: const SizedBox(
134+
width: 100.0,
135+
height: 100.0,
136+
),
137+
),
138+
),
139+
);
140+
141+
await tester.pumpAndSettle();
142+
expect(callCount, equals(2));
143+
});
144+
90145
testWidgets('clamps animated size to constraints', (WidgetTester tester) async {
91146
await tester.pumpWidget(
92147
const Center(

0 commit comments

Comments
 (0)