Unverified Commit 2bb19a95 authored by Fedor Blagodyr's avatar Fedor Blagodyr Committed by GitHub

Added onEnd callback into AnimatedSize (#139859)

close #106439
parent 5a5683dd
...@@ -82,6 +82,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { ...@@ -82,6 +82,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
super.textDirection, super.textDirection,
super.child, super.child,
Clip clipBehavior = Clip.hardEdge, Clip clipBehavior = Clip.hardEdge,
VoidCallback? onEnd,
}) : _vsync = vsync, }) : _vsync = vsync,
_clipBehavior = clipBehavior { _clipBehavior = clipBehavior {
_controller = AnimationController( _controller = AnimationController(
...@@ -97,6 +98,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { ...@@ -97,6 +98,7 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
parent: _controller, parent: _controller,
curve: curve, curve: curve,
); );
_onEnd = onEnd;
} }
/// When asserts are enabled, returns the animation controller that is used /// When asserts are enabled, returns the animation controller that is used
...@@ -203,6 +205,19 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { ...@@ -203,6 +205,19 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
_controller.resync(vsync); _controller.resync(vsync);
} }
/// Called every time an animation completes.
///
/// This can be useful to trigger additional actions (e.g. another animation)
/// at the end of the current animation.
VoidCallback? get onEnd => _onEnd;
VoidCallback? _onEnd;
set onEnd(VoidCallback? value) {
if (value == _onEnd) {
return;
}
_onEnd = value;
}
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
...@@ -216,11 +231,13 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { ...@@ -216,11 +231,13 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
// already, to resume interrupted resizing animation. // already, to resume interrupted resizing animation.
markNeedsLayout(); markNeedsLayout();
} }
_controller.addStatusListener(_animationStatusListener);
} }
@override @override
void detach() { void detach() {
_controller.stop(); _controller.stop();
_controller.removeStatusListener(_animationStatusListener);
super.detach(); super.detach();
} }
...@@ -363,6 +380,16 @@ class RenderAnimatedSize extends RenderAligningShiftedBox { ...@@ -363,6 +380,16 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
} }
} }
void _animationStatusListener(AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
_onEnd?.call();
case AnimationStatus.dismissed:
case AnimationStatus.forward:
case AnimationStatus.reverse:
}
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) { if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) {
......
...@@ -31,6 +31,7 @@ class AnimatedSize extends StatefulWidget { ...@@ -31,6 +31,7 @@ class AnimatedSize extends StatefulWidget {
required this.duration, required this.duration,
this.reverseDuration, this.reverseDuration,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.onEnd,
}); });
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
...@@ -78,6 +79,12 @@ class AnimatedSize extends StatefulWidget { ...@@ -78,6 +79,12 @@ class AnimatedSize extends StatefulWidget {
/// Defaults to [Clip.hardEdge]. /// Defaults to [Clip.hardEdge].
final Clip clipBehavior; final Clip clipBehavior;
/// Called every time an animation completes.
///
/// This can be useful to trigger additional actions (e.g. another animation)
/// at the end of the current animation.
final VoidCallback? onEnd;
@override @override
State<AnimatedSize> createState() => _AnimatedSizeState(); State<AnimatedSize> createState() => _AnimatedSizeState();
} }
...@@ -93,6 +100,7 @@ class _AnimatedSizeState ...@@ -93,6 +100,7 @@ class _AnimatedSizeState
reverseDuration: widget.reverseDuration, reverseDuration: widget.reverseDuration,
vsync: this, vsync: this,
clipBehavior: widget.clipBehavior, clipBehavior: widget.clipBehavior,
onEnd: widget.onEnd,
child: widget.child, child: widget.child,
); );
} }
...@@ -107,6 +115,7 @@ class _AnimatedSize extends SingleChildRenderObjectWidget { ...@@ -107,6 +115,7 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
this.reverseDuration, this.reverseDuration,
required this.vsync, required this.vsync,
this.clipBehavior = Clip.hardEdge, this.clipBehavior = Clip.hardEdge,
this.onEnd,
}); });
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
...@@ -119,6 +128,8 @@ class _AnimatedSize extends SingleChildRenderObjectWidget { ...@@ -119,6 +128,8 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
final Clip clipBehavior; final Clip clipBehavior;
final VoidCallback? onEnd;
@override @override
RenderAnimatedSize createRenderObject(BuildContext context) { RenderAnimatedSize createRenderObject(BuildContext context) {
return RenderAnimatedSize( return RenderAnimatedSize(
...@@ -129,6 +140,7 @@ class _AnimatedSize extends SingleChildRenderObjectWidget { ...@@ -129,6 +140,7 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
vsync: vsync, vsync: vsync,
textDirection: Directionality.maybeOf(context), textDirection: Directionality.maybeOf(context),
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
onEnd: onEnd,
); );
} }
...@@ -141,7 +153,8 @@ class _AnimatedSize extends SingleChildRenderObjectWidget { ...@@ -141,7 +153,8 @@ class _AnimatedSize extends SingleChildRenderObjectWidget {
..curve = curve ..curve = curve
..vsync = vsync ..vsync = vsync
..textDirection = Directionality.maybeOf(context) ..textDirection = Directionality.maybeOf(context)
..clipBehavior = clipBehavior; ..clipBehavior = clipBehavior
..onEnd = onEnd;
} }
@override @override
......
...@@ -87,6 +87,61 @@ void main() { ...@@ -87,6 +87,61 @@ void main() {
expect(box.size.height, equals(100.0)); expect(box.size.height, equals(100.0));
}); });
testWidgets('calls onEnd when animation is completed', (WidgetTester tester) async {
int callCount = 0;
void handleEnd() {
callCount++;
}
await tester.pumpWidget(
Center(
child: AnimatedSize(
onEnd: handleEnd,
duration: const Duration(milliseconds: 200),
child: const SizedBox(
width: 100.0,
height: 100.0,
),
),
),
);
expect(callCount, equals(0));
await tester.pumpWidget(
Center(
child: AnimatedSize(
onEnd: handleEnd,
duration: const Duration(milliseconds: 200),
child: const SizedBox(
width: 200.0,
height: 200.0,
),
),
),
);
expect(callCount, equals(0));
await tester.pumpAndSettle();
expect(callCount, equals(1));
await tester.pumpWidget(
Center(
child: AnimatedSize(
onEnd: handleEnd,
duration: const Duration(milliseconds: 200),
child: const SizedBox(
width: 100.0,
height: 100.0,
),
),
),
);
await tester.pumpAndSettle();
expect(callCount, equals(2));
});
testWidgets('clamps animated size to constraints', (WidgetTester tester) async { testWidgets('clamps animated size to constraints', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const Center( const Center(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment