1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/rendering.dart';
6
7import 'basic.dart';
8import 'framework.dart';
9import 'ticker_provider.dart';
10
11/// Animated widget that automatically transitions its size over a given
12/// duration whenever the given child's size changes.
13///
14/// {@tool dartpad}
15/// This example makes a [Container] react to being touched, causing the child
16/// of the [AnimatedSize] widget, here a [FlutterLogo], to animate.
17///
18/// ** See code in examples/api/lib/widgets/animated_size/animated_size.0.dart **
19/// {@end-tool}
20///
21/// See also:
22///
23/// * [SizeTransition], which changes its size based on an [Animation].
24class AnimatedSize extends StatefulWidget {
25 /// Creates a widget that animates its size to match that of its child.
26 const AnimatedSize({
27 super.key,
28 this.child,
29 this.alignment = Alignment.center,
30 this.curve = Curves.linear,
31 required this.duration,
32 this.reverseDuration,
33 this.clipBehavior = Clip.hardEdge,
34 this.onEnd,
35 });
36
37 /// The widget below this widget in the tree.
38 ///
39 /// {@macro flutter.widgets.ProxyWidget.child}
40 final Widget? child;
41
42 /// The alignment of the child within the parent when the parent is not yet
43 /// the same size as the child.
44 ///
45 /// The x and y values of the alignment control the horizontal and vertical
46 /// alignment, respectively. An x value of -1.0 means that the left edge of
47 /// the child is aligned with the left edge of the parent whereas an x value
48 /// of 1.0 means that the right edge of the child is aligned with the right
49 /// edge of the parent. Other values interpolate (and extrapolate) linearly.
50 /// For example, a value of 0.0 means that the center of the child is aligned
51 /// with the center of the parent.
52 ///
53 /// Defaults to [Alignment.center].
54 ///
55 /// See also:
56 ///
57 /// * [Alignment], a class with convenient constants typically used to
58 /// specify an [AlignmentGeometry].
59 /// * [AlignmentDirectional], like [Alignment] for specifying alignments
60 /// relative to text direction.
61 final AlignmentGeometry alignment;
62
63 /// The animation curve when transitioning this widget's size to match the
64 /// child's size.
65 final Curve curve;
66
67 /// The duration when transitioning this widget's size to match the child's
68 /// size.
69 final Duration duration;
70
71 /// The duration when transitioning this widget's size to match the child's
72 /// size when going in reverse.
73 ///
74 /// If not specified, defaults to [duration].
75 final Duration? reverseDuration;
76
77 /// {@macro flutter.material.Material.clipBehavior}
78 ///
79 /// Defaults to [Clip.hardEdge].
80 final Clip clipBehavior;
81
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
88 @override
89 State<AnimatedSize> createState() => _AnimatedSizeState();
90}
91
92class _AnimatedSizeState
93 extends State<AnimatedSize> with SingleTickerProviderStateMixin {
94 @override
95 Widget build(BuildContext context) {
96 return _AnimatedSize(
97 alignment: widget.alignment,
98 curve: widget.curve,
99 duration: widget.duration,
100 reverseDuration: widget.reverseDuration,
101 vsync: this,
102 clipBehavior: widget.clipBehavior,
103 onEnd: widget.onEnd,
104 child: widget.child,
105 );
106 }
107}
108
109class _AnimatedSize extends SingleChildRenderObjectWidget {
110 const _AnimatedSize({
111 super.child,
112 this.alignment = Alignment.center,
113 this.curve = Curves.linear,
114 required this.duration,
115 this.reverseDuration,
116 required this.vsync,
117 this.clipBehavior = Clip.hardEdge,
118 this.onEnd,
119 });
120
121 final AlignmentGeometry alignment;
122 final Curve curve;
123 final Duration duration;
124 final Duration? reverseDuration;
125
126 /// The [TickerProvider] for this widget.
127 final TickerProvider vsync;
128
129 final Clip clipBehavior;
130
131 final VoidCallback? onEnd;
132
133 @override
134 RenderAnimatedSize createRenderObject(BuildContext context) {
135 return RenderAnimatedSize(
136 alignment: alignment,
137 duration: duration,
138 reverseDuration: reverseDuration,
139 curve: curve,
140 vsync: vsync,
141 textDirection: Directionality.maybeOf(context),
142 clipBehavior: clipBehavior,
143 onEnd: onEnd,
144 );
145 }
146
147 @override
148 void updateRenderObject(BuildContext context, RenderAnimatedSize renderObject) {
149 renderObject
150 ..alignment = alignment
151 ..duration = duration
152 ..reverseDuration = reverseDuration
153 ..curve = curve
154 ..vsync = vsync
155 ..textDirection = Directionality.maybeOf(context)
156 ..clipBehavior = clipBehavior
157 ..onEnd = onEnd;
158 }
159
160 @override
161 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
162 super.debugFillProperties(properties);
163 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: Alignment.topCenter));
164 properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
165 properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
166 }
167}
168