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 | |
5 | /// @docImport 'package:flutter/material.dart'; |
6 | /// |
7 | /// @docImport 'animated_cross_fade.dart'; |
8 | /// @docImport 'animated_size.dart'; |
9 | /// @docImport 'animated_switcher.dart'; |
10 | /// @docImport 'scroll_view.dart'; |
11 | /// @docImport 'sliver.dart'; |
12 | /// @docImport 'tween_animation_builder.dart'; |
13 | library; |
14 | |
15 | import 'dart:ui' as ui show TextHeightBehavior; |
16 | |
17 | import 'package:flutter/foundation.dart'; |
18 | import 'package:flutter/rendering.dart'; |
19 | import 'package:vector_math/vector_math_64.dart' ; |
20 | |
21 | import 'basic.dart'; |
22 | import 'container.dart'; |
23 | import 'debug.dart'; |
24 | import 'framework.dart'; |
25 | import 'text.dart'; |
26 | import 'ticker_provider.dart'; |
27 | import 'transitions.dart'; |
28 | |
29 | // Examples can assume: |
30 | // class MyWidget extends ImplicitlyAnimatedWidget { |
31 | // const MyWidget({super.key, this.targetColor = Colors.black}) : super(duration: const Duration(seconds: 1)); |
32 | // final Color targetColor; |
33 | // @override |
34 | // ImplicitlyAnimatedWidgetState createState() => throw UnimplementedError(); // ignore: no_logic_in_create_state |
35 | // } |
36 | // void setState(VoidCallback fn) { } |
37 | |
38 | /// An interpolation between two [BoxConstraints]. |
39 | /// |
40 | /// This class specializes the interpolation of [Tween<BoxConstraints>] to use |
41 | /// [BoxConstraints.lerp]. |
42 | /// |
43 | /// See [Tween] for a discussion on how to use interpolation objects. |
44 | class BoxConstraintsTween extends Tween<BoxConstraints> { |
45 | /// Creates a [BoxConstraints] tween. |
46 | /// |
47 | /// The [begin] and [end] properties may be null; the null value |
48 | /// is treated as a tight constraint of zero size. |
49 | BoxConstraintsTween({ super.begin, super.end }); |
50 | |
51 | /// Returns the value this variable has at the given animation clock value. |
52 | @override |
53 | BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t)!; |
54 | } |
55 | |
56 | /// An interpolation between two [Decoration]s. |
57 | /// |
58 | /// This class specializes the interpolation of [Tween<BoxConstraints>] to use |
59 | /// [Decoration.lerp]. |
60 | /// |
61 | /// For [ShapeDecoration]s which know how to [ShapeDecoration.lerpTo] or |
62 | /// [ShapeDecoration.lerpFrom] each other, this will produce a smooth |
63 | /// interpolation between decorations. |
64 | /// |
65 | /// See also: |
66 | /// |
67 | /// * [Tween] for a discussion on how to use interpolation objects. |
68 | /// * [ShapeDecoration], [RoundedRectangleBorder], [CircleBorder], and |
69 | /// [StadiumBorder] for examples of shape borders that can be smoothly |
70 | /// interpolated. |
71 | /// * [BoxBorder] for a border that can only be smoothly interpolated between other |
72 | /// [BoxBorder]s. |
73 | class DecorationTween extends Tween<Decoration> { |
74 | /// Creates a decoration tween. |
75 | /// |
76 | /// The [begin] and [end] properties may be null. If both are null, then the |
77 | /// result is always null. If [end] is not null, then its lerping logic is |
78 | /// used (via [Decoration.lerpTo]). Otherwise, [begin]'s lerping logic is used |
79 | /// (via [Decoration.lerpFrom]). |
80 | DecorationTween({ super.begin, super.end }); |
81 | |
82 | /// Returns the value this variable has at the given animation clock value. |
83 | @override |
84 | Decoration lerp(double t) => Decoration.lerp(begin, end, t)!; |
85 | } |
86 | |
87 | /// An interpolation between two [EdgeInsets]s. |
88 | /// |
89 | /// This class specializes the interpolation of [Tween<EdgeInsets>] to use |
90 | /// [EdgeInsets.lerp]. |
91 | /// |
92 | /// See [Tween] for a discussion on how to use interpolation objects. |
93 | /// |
94 | /// See also: |
95 | /// |
96 | /// * [EdgeInsetsGeometryTween], which interpolates between two |
97 | /// [EdgeInsetsGeometry] objects. |
98 | class EdgeInsetsTween extends Tween<EdgeInsets> { |
99 | /// Creates an [EdgeInsets] tween. |
100 | /// |
101 | /// The [begin] and [end] properties may be null; the null value |
102 | /// is treated as an [EdgeInsets] with no inset. |
103 | EdgeInsetsTween({ super.begin, super.end }); |
104 | |
105 | /// Returns the value this variable has at the given animation clock value. |
106 | @override |
107 | EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t)!; |
108 | } |
109 | |
110 | /// An interpolation between two [EdgeInsetsGeometry]s. |
111 | /// |
112 | /// This class specializes the interpolation of [Tween<EdgeInsetsGeometry>] to |
113 | /// use [EdgeInsetsGeometry.lerp]. |
114 | /// |
115 | /// See [Tween] for a discussion on how to use interpolation objects. |
116 | /// |
117 | /// See also: |
118 | /// |
119 | /// * [EdgeInsetsTween], which interpolates between two [EdgeInsets] objects. |
120 | class EdgeInsetsGeometryTween extends Tween<EdgeInsetsGeometry> { |
121 | /// Creates an [EdgeInsetsGeometry] tween. |
122 | /// |
123 | /// The [begin] and [end] properties may be null; the null value |
124 | /// is treated as an [EdgeInsetsGeometry] with no inset. |
125 | EdgeInsetsGeometryTween({ super.begin, super.end }); |
126 | |
127 | /// Returns the value this variable has at the given animation clock value. |
128 | @override |
129 | EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t)!; |
130 | } |
131 | |
132 | /// An interpolation between two [BorderRadius]s. |
133 | /// |
134 | /// This class specializes the interpolation of [Tween<BorderRadius>] to use |
135 | /// [BorderRadius.lerp]. |
136 | /// |
137 | /// See [Tween] for a discussion on how to use interpolation objects. |
138 | class BorderRadiusTween extends Tween<BorderRadius?> { |
139 | /// Creates a [BorderRadius] tween. |
140 | /// |
141 | /// The [begin] and [end] properties may be null; the null value |
142 | /// is treated as a right angle (no radius). |
143 | BorderRadiusTween({ super.begin, super.end }); |
144 | |
145 | /// Returns the value this variable has at the given animation clock value. |
146 | @override |
147 | BorderRadius? lerp(double t) => BorderRadius.lerp(begin, end, t); |
148 | } |
149 | |
150 | /// An interpolation between two [Border]s. |
151 | /// |
152 | /// This class specializes the interpolation of [Tween<Border>] to use |
153 | /// [Border.lerp]. |
154 | /// |
155 | /// See [Tween] for a discussion on how to use interpolation objects. |
156 | class BorderTween extends Tween<Border?> { |
157 | /// Creates a [Border] tween. |
158 | /// |
159 | /// The [begin] and [end] properties may be null; the null value |
160 | /// is treated as having no border. |
161 | BorderTween({ super.begin, super.end }); |
162 | |
163 | /// Returns the value this variable has at the given animation clock value. |
164 | @override |
165 | Border? lerp(double t) => Border.lerp(begin, end, t); |
166 | } |
167 | |
168 | /// An interpolation between two [Matrix4]s. |
169 | /// |
170 | /// This class specializes the interpolation of [Tween<Matrix4>] to be |
171 | /// appropriate for transformation matrices. |
172 | /// |
173 | /// Currently this class works only for translations. |
174 | /// |
175 | /// See [Tween] for a discussion on how to use interpolation objects. |
176 | class Matrix4Tween extends Tween<Matrix4> { |
177 | /// Creates a [Matrix4] tween. |
178 | /// |
179 | /// The [begin] and [end] properties must be non-null before the tween is |
180 | /// first used, but the arguments can be null if the values are going to be |
181 | /// filled in later. |
182 | Matrix4Tween({ super.begin, super.end }); |
183 | |
184 | @override |
185 | Matrix4 lerp(double t) { |
186 | assert(begin != null); |
187 | assert(end != null); |
188 | final Vector3 beginTranslation = Vector3.zero(); |
189 | final Vector3 endTranslation = Vector3.zero(); |
190 | final Quaternion beginRotation = Quaternion.identity(); |
191 | final Quaternion endRotation = Quaternion.identity(); |
192 | final Vector3 beginScale = Vector3.zero(); |
193 | final Vector3 endScale = Vector3.zero(); |
194 | begin!.decompose(beginTranslation, beginRotation, beginScale); |
195 | end!.decompose(endTranslation, endRotation, endScale); |
196 | final Vector3 lerpTranslation = |
197 | beginTranslation * (1.0 - t) + endTranslation * t; |
198 | // TODO(alangardner): Implement lerp for constant rotation |
199 | final Quaternion lerpRotation = |
200 | (beginRotation.scaled(1.0 - t) + endRotation.scaled(t)).normalized(); |
201 | final Vector3 lerpScale = beginScale * (1.0 - t) + endScale * t; |
202 | return Matrix4.compose(lerpTranslation, lerpRotation, lerpScale); |
203 | } |
204 | } |
205 | |
206 | /// An interpolation between two [TextStyle]s. |
207 | /// |
208 | /// This class specializes the interpolation of [Tween<TextStyle>] to use |
209 | /// [TextStyle.lerp]. |
210 | /// |
211 | /// This will not work well if the styles don't set the same fields. |
212 | /// |
213 | /// See [Tween] for a discussion on how to use interpolation objects. |
214 | class TextStyleTween extends Tween<TextStyle> { |
215 | /// Creates a text style tween. |
216 | /// |
217 | /// The [begin] and [end] properties must be non-null before the tween is |
218 | /// first used, but the arguments can be null if the values are going to be |
219 | /// filled in later. |
220 | TextStyleTween({ super.begin, super.end }); |
221 | |
222 | /// Returns the value this variable has at the given animation clock value. |
223 | @override |
224 | TextStyle lerp(double t) => TextStyle.lerp(begin, end, t)!; |
225 | } |
226 | |
227 | /// An abstract class for building widgets that animate changes to their |
228 | /// properties. |
229 | /// |
230 | /// Widgets of this type will not animate when they are first added to the |
231 | /// widget tree. Rather, when they are rebuilt with different values, they will |
232 | /// respond to those _changes_ by animating the changes over a specified |
233 | /// [duration]. |
234 | /// |
235 | /// Which properties are animated is left up to the subclass. Subclasses' [State]s |
236 | /// must extend [ImplicitlyAnimatedWidgetState] and provide a way to visit the |
237 | /// relevant fields to animate. |
238 | /// |
239 | /// ## Relationship to [AnimatedWidget]s |
240 | /// |
241 | /// [ImplicitlyAnimatedWidget]s (and their subclasses) automatically animate |
242 | /// changes in their properties whenever they change. For this, |
243 | /// they create and manage their own internal [AnimationController]s to power |
244 | /// the animation. While these widgets are simple to use and don't require you |
245 | /// to manually manage the lifecycle of an [AnimationController], they |
246 | /// are also somewhat limited: Besides the target value for the animated |
247 | /// property, developers can only choose a [duration] and [curve] for the |
248 | /// animation. If you require more control over the animation (e.g. you want |
249 | /// to stop it somewhere in the middle), consider using an |
250 | /// [AnimatedWidget] or one of its subclasses. These widgets take an [Animation] |
251 | /// as an argument to power the animation. This gives the developer full control |
252 | /// over the animation at the cost of requiring you to manually manage the |
253 | /// underlying [AnimationController]. |
254 | /// |
255 | /// ## Common implicitly animated widgets |
256 | /// |
257 | /// A number of implicitly animated widgets ship with the framework. They are |
258 | /// usually named `AnimatedFoo`, where `Foo` is the name of the non-animated |
259 | /// version of that widget. Commonly used implicitly animated widgets include: |
260 | /// |
261 | /// * [TweenAnimationBuilder], which animates any property expressed by |
262 | /// a [Tween] to a specified target value. |
263 | /// * [AnimatedAlign], which is an implicitly animated version of [Align]. |
264 | /// * [AnimatedContainer], which is an implicitly animated version of |
265 | /// [Container]. |
266 | /// * [AnimatedDefaultTextStyle], which is an implicitly animated version of |
267 | /// [DefaultTextStyle]. |
268 | /// * [AnimatedScale], which is an implicitly animated version of [Transform.scale]. |
269 | /// * [AnimatedRotation], which is an implicitly animated version of [Transform.rotate]. |
270 | /// * [AnimatedSlide], which implicitly animates the position of a widget relative to its normal position. |
271 | /// * [AnimatedOpacity], which is an implicitly animated version of [Opacity]. |
272 | /// * [AnimatedPadding], which is an implicitly animated version of [Padding]. |
273 | /// * [AnimatedPhysicalModel], which is an implicitly animated version of |
274 | /// [PhysicalModel]. |
275 | /// * [AnimatedPositioned], which is an implicitly animated version of |
276 | /// [Positioned]. |
277 | /// * [AnimatedPositionedDirectional], which is an implicitly animated version |
278 | /// of [PositionedDirectional]. |
279 | /// * [AnimatedTheme], which is an implicitly animated version of [Theme]. |
280 | /// * [AnimatedCrossFade], which cross-fades between two given children and |
281 | /// animates itself between their sizes. |
282 | /// * [AnimatedSize], which automatically transitions its size over a given |
283 | /// duration. |
284 | /// * [AnimatedSwitcher], which fades from one widget to another. |
285 | abstract class ImplicitlyAnimatedWidget extends StatefulWidget { |
286 | /// Initializes fields for subclasses. |
287 | const ImplicitlyAnimatedWidget({ |
288 | super.key, |
289 | this.curve = Curves.linear, |
290 | required this.duration, |
291 | this.onEnd, |
292 | }); |
293 | |
294 | /// The curve to apply when animating the parameters of this container. |
295 | final Curve curve; |
296 | |
297 | /// The duration over which to animate the parameters of this container. |
298 | final Duration duration; |
299 | |
300 | /// Called every time an animation completes. |
301 | /// |
302 | /// This can be useful to trigger additional actions (e.g. another animation) |
303 | /// at the end of the current animation. |
304 | final VoidCallback? onEnd; |
305 | |
306 | @override |
307 | ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState(); |
308 | |
309 | @override |
310 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
311 | super.debugFillProperties(properties); |
312 | properties.add(IntProperty('duration' , duration.inMilliseconds, unit: 'ms' )); |
313 | } |
314 | } |
315 | |
316 | /// Signature for a [Tween] factory. |
317 | /// |
318 | /// This is the type of one of the arguments of [TweenVisitor], the signature |
319 | /// used by [AnimatedWidgetBaseState.forEachTween]. |
320 | /// |
321 | /// Instances of this function are expected to take a value and return a tween |
322 | /// beginning at that value. |
323 | typedef TweenConstructor<T extends Object> = Tween<T> Function(T targetValue); |
324 | |
325 | /// Signature for callbacks passed to [ImplicitlyAnimatedWidgetState.forEachTween]. |
326 | /// |
327 | /// {@template flutter.widgets.TweenVisitor.arguments} |
328 | /// The `tween` argument should contain the current tween value. This will |
329 | /// initially be null when the state is first initialized. |
330 | /// |
331 | /// The `targetValue` argument should contain the value toward which the state |
332 | /// is animating. For instance, if the state is animating its widget's |
333 | /// opacity value, then this argument should contain the widget's current |
334 | /// opacity value. |
335 | /// |
336 | /// The `constructor` argument should contain a function that takes a value |
337 | /// (the widget's value being animated) and returns a tween beginning at that |
338 | /// value. |
339 | /// |
340 | /// {@endtemplate} |
341 | /// |
342 | /// `forEachTween()` is expected to update its tween value to the return value |
343 | /// of this visitor. |
344 | /// |
345 | /// The `<T>` parameter specifies the type of value that's being animated. |
346 | typedef TweenVisitor<T extends Object> = Tween<T>? Function(Tween<T>? tween, T targetValue, TweenConstructor<T> constructor); |
347 | |
348 | /// A base class for the `State` of widgets with implicit animations. |
349 | /// |
350 | /// [ImplicitlyAnimatedWidgetState] requires that subclasses respond to the |
351 | /// animation themselves. If you would like `setState()` to be called |
352 | /// automatically as the animation changes, use [AnimatedWidgetBaseState]. |
353 | /// |
354 | /// Properties that subclasses choose to animate are represented by [Tween] |
355 | /// instances. Subclasses must implement the [forEachTween] method to allow |
356 | /// [ImplicitlyAnimatedWidgetState] to iterate through the widget's fields and |
357 | /// animate them. |
358 | abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> { |
359 | /// The animation controller driving this widget's implicit animations. |
360 | @protected |
361 | AnimationController get controller => _controller; |
362 | late final AnimationController _controller = AnimationController( |
363 | duration: widget.duration, |
364 | debugLabel: kDebugMode ? widget.toStringShort() : null, |
365 | vsync: this, |
366 | ); |
367 | |
368 | /// The animation driving this widget's implicit animations. |
369 | Animation<double> get animation => _animation; |
370 | late CurvedAnimation _animation = _createCurve(); |
371 | |
372 | @override |
373 | void initState() { |
374 | super.initState(); |
375 | _controller.addStatusListener((AnimationStatus status) { |
376 | if (status.isCompleted) { |
377 | widget.onEnd?.call(); |
378 | } |
379 | }); |
380 | _constructTweens(); |
381 | didUpdateTweens(); |
382 | } |
383 | |
384 | @override |
385 | void didUpdateWidget(T oldWidget) { |
386 | super.didUpdateWidget(oldWidget); |
387 | if (widget.curve != oldWidget.curve) { |
388 | _animation.dispose(); |
389 | _animation = _createCurve(); |
390 | } |
391 | _controller.duration = widget.duration; |
392 | if (_constructTweens()) { |
393 | forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) { |
394 | _updateTween(tween, targetValue); |
395 | return tween; |
396 | }); |
397 | _controller |
398 | ..value = 0.0 |
399 | ..forward(); |
400 | didUpdateTweens(); |
401 | } |
402 | } |
403 | |
404 | CurvedAnimation _createCurve() { |
405 | return CurvedAnimation(parent: _controller, curve: widget.curve); |
406 | } |
407 | |
408 | @override |
409 | void dispose() { |
410 | _animation.dispose(); |
411 | _controller.dispose(); |
412 | super.dispose(); |
413 | } |
414 | |
415 | bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) { |
416 | return targetValue != (tween.end ?? tween.begin); |
417 | } |
418 | |
419 | void _updateTween(Tween<dynamic>? tween, dynamic targetValue) { |
420 | if (tween == null) { |
421 | return; |
422 | } |
423 | tween |
424 | ..begin = tween.evaluate(_animation) |
425 | ..end = targetValue; |
426 | } |
427 | |
428 | bool _constructTweens() { |
429 | bool shouldStartAnimation = false; |
430 | forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) { |
431 | if (targetValue != null) { |
432 | tween ??= constructor(targetValue); |
433 | if (_shouldAnimateTween(tween, targetValue)) { |
434 | shouldStartAnimation = true; |
435 | } else { |
436 | tween.end ??= tween.begin; |
437 | } |
438 | } else { |
439 | tween = null; |
440 | } |
441 | return tween; |
442 | }); |
443 | return shouldStartAnimation; |
444 | } |
445 | |
446 | /// Visits each tween controlled by this state with the specified `visitor` |
447 | /// function. |
448 | /// |
449 | /// ### Subclass responsibility |
450 | /// |
451 | /// Properties to be animated are represented by [Tween] member variables in |
452 | /// the state. For each such tween, [forEachTween] implementations are |
453 | /// expected to call `visitor` with the appropriate arguments and store the |
454 | /// result back into the member variable. The arguments to `visitor` are as |
455 | /// follows: |
456 | /// |
457 | /// {@macro flutter.widgets.TweenVisitor.arguments} |
458 | /// |
459 | /// ### When this method will be called |
460 | /// |
461 | /// [forEachTween] is initially called during [initState]. It is expected that |
462 | /// the visitor's `tween` argument will be set to null, causing the visitor to |
463 | /// call its `constructor` argument to construct the tween for the first time. |
464 | /// The resulting tween will have its `begin` value set to the target value |
465 | /// and will have its `end` value set to null. The animation will not be |
466 | /// started. |
467 | /// |
468 | /// When this state's [widget] is updated (thus triggering the |
469 | /// [didUpdateWidget] method to be called), [forEachTween] will be called |
470 | /// again to check if the target value has changed. If the target value has |
471 | /// changed, signaling that the [animation] should start, then the visitor |
472 | /// will update the tween's `start` and `end` values accordingly, and the |
473 | /// animation will be started. |
474 | /// |
475 | /// ### Other member variables |
476 | /// |
477 | /// Subclasses that contain properties based on tweens created by |
478 | /// [forEachTween] should override [didUpdateTweens] to update those |
479 | /// properties. Dependent properties should not be updated within |
480 | /// [forEachTween]. |
481 | /// |
482 | /// {@tool snippet} |
483 | /// |
484 | /// This sample implements an implicitly animated widget's `State`. |
485 | /// The widget animates between colors whenever `widget.targetColor` |
486 | /// changes. |
487 | /// |
488 | /// ```dart |
489 | /// class MyWidgetState extends AnimatedWidgetBaseState<MyWidget> { |
490 | /// ColorTween? _colorTween; |
491 | /// |
492 | /// @override |
493 | /// Widget build(BuildContext context) { |
494 | /// return Text( |
495 | /// 'Hello World', |
496 | /// // Computes the value of the text color at any given time. |
497 | /// style: TextStyle(color: _colorTween?.evaluate(animation)), |
498 | /// ); |
499 | /// } |
500 | /// |
501 | /// @override |
502 | /// void forEachTween(TweenVisitor<dynamic> visitor) { |
503 | /// // Update the tween using the provided visitor function. |
504 | /// _colorTween = visitor( |
505 | /// // The latest tween value. Can be `null`. |
506 | /// _colorTween, |
507 | /// // The color value toward which we are animating. |
508 | /// widget.targetColor, |
509 | /// // A function that takes a color value and returns a tween |
510 | /// // beginning at that value. |
511 | /// (dynamic value) => ColorTween(begin: value as Color?), |
512 | /// ) as ColorTween?; |
513 | /// |
514 | /// // We could have more tweens than one by using the visitor |
515 | /// // multiple times. |
516 | /// } |
517 | /// } |
518 | /// ``` |
519 | /// {@end-tool} |
520 | @protected |
521 | void forEachTween(TweenVisitor<dynamic> visitor); |
522 | |
523 | /// Optional hook for subclasses that runs after all tweens have been updated |
524 | /// via [forEachTween]. |
525 | /// |
526 | /// Any properties that depend upon tweens created by [forEachTween] should be |
527 | /// updated within [didUpdateTweens], not within [forEachTween]. |
528 | /// |
529 | /// This method will be called both: |
530 | /// |
531 | /// 1. After the tweens are _initially_ constructed (by |
532 | /// the `constructor` argument to the [TweenVisitor] that's passed to |
533 | /// [forEachTween]). In this case, the tweens are likely to contain only |
534 | /// a [Tween.begin] value and not a [Tween.end]. |
535 | /// |
536 | /// 2. When the state's [widget] is updated, and one or more of the tweens |
537 | /// visited by [forEachTween] specifies a target value that's different |
538 | /// than the widget's current value, thus signaling that the [animation] |
539 | /// should run. In this case, the [Tween.begin] value for each tween will |
540 | /// an evaluation of the tween against the current [animation], and the |
541 | /// [Tween.end] value for each tween will be the target value. |
542 | @protected |
543 | void didUpdateTweens() { } |
544 | } |
545 | |
546 | /// A base class for widgets with implicit animations that need to rebuild their |
547 | /// widget tree as the animation runs. |
548 | /// |
549 | /// This class calls [build] each frame that the animation ticks. For a |
550 | /// variant that does not rebuild each frame, consider subclassing |
551 | /// [ImplicitlyAnimatedWidgetState] directly. |
552 | /// |
553 | /// Subclasses must implement the [forEachTween] method to allow |
554 | /// [AnimatedWidgetBaseState] to iterate through the subclasses' widget's fields |
555 | /// and animate them. |
556 | abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> extends ImplicitlyAnimatedWidgetState<T> { |
557 | @override |
558 | void initState() { |
559 | super.initState(); |
560 | controller.addListener(_handleAnimationChanged); |
561 | } |
562 | |
563 | void _handleAnimationChanged() { |
564 | setState(() { /* The animation ticked. Rebuild with new animation value */ }); |
565 | } |
566 | } |
567 | |
568 | /// Animated version of [Container] that gradually changes its values over a period of time. |
569 | /// |
570 | /// The [AnimatedContainer] will automatically animate between the old and |
571 | /// new values of properties when they change using the provided curve and |
572 | /// duration. Properties that are null are not animated. Its child and |
573 | /// descendants are not animated. |
574 | /// |
575 | /// This class is useful for generating simple implicit transitions between |
576 | /// different parameters to [Container] with its internal [AnimationController]. |
577 | /// For more complex animations, you'll likely want to use a subclass of |
578 | /// [AnimatedWidget] such as the [DecoratedBoxTransition] or use your own |
579 | /// [AnimationController]. |
580 | /// |
581 | /// {@youtube 560 315 https://www.youtube.com/watch?v=yI-8QHpGIP4} |
582 | /// |
583 | /// {@tool dartpad} |
584 | /// The following example (depicted above) transitions an AnimatedContainer |
585 | /// between two states. It adjusts the `height`, `width`, `color`, and |
586 | /// [alignment] properties when tapped. |
587 | /// |
588 | /// ** See code in examples/api/lib/widgets/implicit_animations/animated_container.0.dart ** |
589 | /// {@end-tool} |
590 | /// |
591 | /// See also: |
592 | /// |
593 | /// * [AnimatedPadding], which is a subset of this widget that only |
594 | /// supports animating the [padding]. |
595 | /// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/). |
596 | /// * [AnimatedPositioned], which, as a child of a [Stack], automatically |
597 | /// transitions its child's position over a given duration whenever the given |
598 | /// position changes. |
599 | /// * [AnimatedAlign], which automatically transitions its child's |
600 | /// position over a given duration whenever the given [AnimatedAlign.alignment] changes. |
601 | /// * [AnimatedSwitcher], which switches out a child for a new one with a customizable transition. |
602 | /// * [AnimatedCrossFade], which fades between two children and interpolates their sizes. |
603 | class AnimatedContainer extends ImplicitlyAnimatedWidget { |
604 | /// Creates a container that animates its parameters implicitly. |
605 | AnimatedContainer({ |
606 | super.key, |
607 | this.alignment, |
608 | this.padding, |
609 | Color? color, |
610 | Decoration? decoration, |
611 | this.foregroundDecoration, |
612 | double? width, |
613 | double? height, |
614 | BoxConstraints? constraints, |
615 | this.margin, |
616 | this.transform, |
617 | this.transformAlignment, |
618 | this.child, |
619 | this.clipBehavior = Clip.none, |
620 | super.curve, |
621 | required super.duration, |
622 | super.onEnd, |
623 | }) : assert(margin == null || margin.isNonNegative), |
624 | assert(padding == null || padding.isNonNegative), |
625 | assert(decoration == null || decoration.debugAssertIsValid()), |
626 | assert(constraints == null || constraints.debugAssertIsValid()), |
627 | assert(color == null || decoration == null, |
628 | 'Cannot provide both a color and a decoration\n' |
629 | 'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".' , |
630 | ), |
631 | decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null), |
632 | constraints = |
633 | (width != null || height != null) |
634 | ? constraints?.tighten(width: width, height: height) |
635 | ?? BoxConstraints.tightFor(width: width, height: height) |
636 | : constraints; |
637 | |
638 | /// The [child] contained by the container. |
639 | /// |
640 | /// If null, and if the [constraints] are unbounded or also null, the |
641 | /// container will expand to fill all available space in its parent, unless |
642 | /// the parent provides unbounded constraints, in which case the container |
643 | /// will attempt to be as small as possible. |
644 | /// |
645 | /// {@macro flutter.widgets.ProxyWidget.child} |
646 | final Widget? child; |
647 | |
648 | /// Align the [child] within the container. |
649 | /// |
650 | /// If non-null, the container will expand to fill its parent and position its |
651 | /// child within itself according to the given value. If the incoming |
652 | /// constraints are unbounded, then the child will be shrink-wrapped instead. |
653 | /// |
654 | /// Ignored if [child] is null. |
655 | /// |
656 | /// See also: |
657 | /// |
658 | /// * [Alignment], a class with convenient constants typically used to |
659 | /// specify an [AlignmentGeometry]. |
660 | /// * [AlignmentDirectional], like [Alignment] for specifying alignments |
661 | /// relative to text direction. |
662 | final AlignmentGeometry? alignment; |
663 | |
664 | /// Empty space to inscribe inside the [decoration]. The [child], if any, is |
665 | /// placed inside this padding. |
666 | final EdgeInsetsGeometry? padding; |
667 | |
668 | /// The decoration to paint behind the [child]. |
669 | /// |
670 | /// A shorthand for specifying just a solid color is available in the |
671 | /// constructor: set the `color` argument instead of the `decoration` |
672 | /// argument. |
673 | final Decoration? decoration; |
674 | |
675 | /// The decoration to paint in front of the child. |
676 | final Decoration? foregroundDecoration; |
677 | |
678 | /// Additional constraints to apply to the child. |
679 | /// |
680 | /// The constructor `width` and `height` arguments are combined with the |
681 | /// `constraints` argument to set this property. |
682 | /// |
683 | /// The [padding] goes inside the constraints. |
684 | final BoxConstraints? constraints; |
685 | |
686 | /// Empty space to surround the [decoration] and [child]. |
687 | final EdgeInsetsGeometry? margin; |
688 | |
689 | /// The transformation matrix to apply before painting the container. |
690 | final Matrix4? transform; |
691 | |
692 | /// The alignment of the origin, relative to the size of the container, if [transform] is specified. |
693 | /// |
694 | /// When [transform] is null, the value of this property is ignored. |
695 | /// |
696 | /// See also: |
697 | /// |
698 | /// * [Transform.alignment], which is set by this property. |
699 | final AlignmentGeometry? transformAlignment; |
700 | |
701 | /// The clip behavior when [AnimatedContainer.decoration] is not null. |
702 | /// |
703 | /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null. |
704 | /// |
705 | /// Unlike other properties of [AnimatedContainer], changes to this property |
706 | /// apply immediately and have no animation. |
707 | /// |
708 | /// If a clip is to be applied, the [Decoration.getClipPath] method |
709 | /// for the provided decoration must return a clip path. (This is not |
710 | /// supported by all decorations; the default implementation of that |
711 | /// method throws an [UnsupportedError].) |
712 | final Clip clipBehavior; |
713 | |
714 | @override |
715 | AnimatedWidgetBaseState<AnimatedContainer> createState() => _AnimatedContainerState(); |
716 | |
717 | @override |
718 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
719 | super.debugFillProperties(properties); |
720 | properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment' , alignment, showName: false, defaultValue: null)); |
721 | properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding' , padding, defaultValue: null)); |
722 | properties.add(DiagnosticsProperty<Decoration>('bg' , decoration, defaultValue: null)); |
723 | properties.add(DiagnosticsProperty<Decoration>('fg' , foregroundDecoration, defaultValue: null)); |
724 | properties.add(DiagnosticsProperty<BoxConstraints>('constraints' , constraints, defaultValue: null, showName: false)); |
725 | properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin' , margin, defaultValue: null)); |
726 | properties.add(ObjectFlagProperty<Matrix4>.has('transform' , transform)); |
727 | properties.add(DiagnosticsProperty<AlignmentGeometry>('transformAlignment' , transformAlignment, defaultValue: null)); |
728 | properties.add(DiagnosticsProperty<Clip>('clipBehavior' , clipBehavior)); |
729 | } |
730 | } |
731 | |
732 | class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> { |
733 | AlignmentGeometryTween? _alignment; |
734 | EdgeInsetsGeometryTween? _padding; |
735 | DecorationTween? _decoration; |
736 | DecorationTween? _foregroundDecoration; |
737 | BoxConstraintsTween? _constraints; |
738 | EdgeInsetsGeometryTween? _margin; |
739 | Matrix4Tween? _transform; |
740 | AlignmentGeometryTween? _transformAlignment; |
741 | |
742 | @override |
743 | void forEachTween(TweenVisitor<dynamic> visitor) { |
744 | _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?; |
745 | _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?; |
746 | _decoration = visitor(_decoration, widget.decoration, (dynamic value) => DecorationTween(begin: value as Decoration)) as DecorationTween?; |
747 | _foregroundDecoration = visitor(_foregroundDecoration, widget.foregroundDecoration, (dynamic value) => DecorationTween(begin: value as Decoration)) as DecorationTween?; |
748 | _constraints = visitor(_constraints, widget.constraints, (dynamic value) => BoxConstraintsTween(begin: value as BoxConstraints)) as BoxConstraintsTween?; |
749 | _margin = visitor(_margin, widget.margin, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?; |
750 | _transform = visitor(_transform, widget.transform, (dynamic value) => Matrix4Tween(begin: value as Matrix4)) as Matrix4Tween?; |
751 | _transformAlignment = visitor(_transformAlignment, widget.transformAlignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?; |
752 | } |
753 | |
754 | @override |
755 | Widget build(BuildContext context) { |
756 | final Animation<double> animation = this.animation; |
757 | return Container( |
758 | alignment: _alignment?.evaluate(animation), |
759 | padding: _padding?.evaluate(animation), |
760 | decoration: _decoration?.evaluate(animation), |
761 | foregroundDecoration: _foregroundDecoration?.evaluate(animation), |
762 | constraints: _constraints?.evaluate(animation), |
763 | margin: _margin?.evaluate(animation), |
764 | transform: _transform?.evaluate(animation), |
765 | transformAlignment: _transformAlignment?.evaluate(animation), |
766 | clipBehavior: widget.clipBehavior, |
767 | child: widget.child, |
768 | ); |
769 | } |
770 | |
771 | @override |
772 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
773 | super.debugFillProperties(description); |
774 | description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment' , _alignment, showName: false, defaultValue: null)); |
775 | description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('padding' , _padding, defaultValue: null)); |
776 | description.add(DiagnosticsProperty<DecorationTween>('bg' , _decoration, defaultValue: null)); |
777 | description.add(DiagnosticsProperty<DecorationTween>('fg' , _foregroundDecoration, defaultValue: null)); |
778 | description.add(DiagnosticsProperty<BoxConstraintsTween>('constraints' , _constraints, showName: false, defaultValue: null)); |
779 | description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('margin' , _margin, defaultValue: null)); |
780 | description.add(ObjectFlagProperty<Matrix4Tween>.has('transform' , _transform)); |
781 | description.add(DiagnosticsProperty<AlignmentGeometryTween>('transformAlignment' , _transformAlignment, defaultValue: null)); |
782 | } |
783 | } |
784 | |
785 | /// Animated version of [Padding] which automatically transitions the |
786 | /// indentation over a given duration whenever the given inset changes. |
787 | /// |
788 | /// {@youtube 560 315 https://www.youtube.com/watch?v=PY2m0fhGNz4} |
789 | /// |
790 | /// Here's an illustration of what using this widget looks like, using a [curve] |
791 | /// of [Curves.fastOutSlowIn]. |
792 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_padding.mp4} |
793 | /// |
794 | /// {@tool dartpad} |
795 | /// The following code implements the [AnimatedPadding] widget, using a [curve] of |
796 | /// [Curves.easeInOut]. |
797 | /// |
798 | /// ** See code in examples/api/lib/widgets/implicit_animations/animated_padding.0.dart ** |
799 | /// {@end-tool} |
800 | /// |
801 | /// See also: |
802 | /// |
803 | /// * [AnimatedContainer], which can transition more values at once. |
804 | /// * [AnimatedAlign], which automatically transitions its child's |
805 | /// position over a given duration whenever the given |
806 | /// [AnimatedAlign.alignment] changes. |
807 | class AnimatedPadding extends ImplicitlyAnimatedWidget { |
808 | /// Creates a widget that insets its child by a value that animates |
809 | /// implicitly. |
810 | AnimatedPadding({ |
811 | super.key, |
812 | required this.padding, |
813 | this.child, |
814 | super.curve, |
815 | required super.duration, |
816 | super.onEnd, |
817 | }) : assert(padding.isNonNegative); |
818 | |
819 | /// The amount of space by which to inset the child. |
820 | final EdgeInsetsGeometry padding; |
821 | |
822 | /// The widget below this widget in the tree. |
823 | /// |
824 | /// {@macro flutter.widgets.ProxyWidget.child} |
825 | final Widget? child; |
826 | |
827 | @override |
828 | AnimatedWidgetBaseState<AnimatedPadding> createState() => _AnimatedPaddingState(); |
829 | |
830 | @override |
831 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
832 | super.debugFillProperties(properties); |
833 | properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding' , padding)); |
834 | } |
835 | } |
836 | |
837 | class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> { |
838 | EdgeInsetsGeometryTween? _padding; |
839 | |
840 | @override |
841 | void forEachTween(TweenVisitor<dynamic> visitor) { |
842 | _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?; |
843 | } |
844 | |
845 | @override |
846 | Widget build(BuildContext context) { |
847 | return Padding( |
848 | padding: _padding! |
849 | .evaluate(animation) |
850 | .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), |
851 | child: widget.child, |
852 | ); |
853 | } |
854 | |
855 | @override |
856 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
857 | super.debugFillProperties(description); |
858 | description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('padding' , _padding, defaultValue: null)); |
859 | } |
860 | } |
861 | |
862 | /// Animated version of [Align] which automatically transitions the child's |
863 | /// position over a given duration whenever the given [alignment] changes. |
864 | /// |
865 | /// Here's an illustration of what this can look like, using a [curve] of |
866 | /// [Curves.fastOutSlowIn]. |
867 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_align.mp4} |
868 | /// |
869 | /// For the animation, you can choose a [curve] as well as a [duration] and the |
870 | /// widget will automatically animate to the new target [alignment]. If you require |
871 | /// more control over the animation (e.g. if you want to stop it mid-animation), |
872 | /// consider using an [AlignTransition] instead, which takes a provided |
873 | /// [Animation] as argument. While that allows you to fine-tune the animation, |
874 | /// it also requires more development overhead as you have to manually manage |
875 | /// the lifecycle of the underlying [AnimationController]. |
876 | /// |
877 | /// {@tool dartpad} |
878 | /// The following code implements the [AnimatedAlign] widget, using a [curve] of |
879 | /// [Curves.fastOutSlowIn]. |
880 | /// |
881 | /// ** See code in examples/api/lib/widgets/implicit_animations/animated_align.0.dart ** |
882 | /// {@end-tool} |
883 | /// |
884 | /// See also: |
885 | /// |
886 | /// * [AnimatedContainer], which can transition more values at once. |
887 | /// * [AnimatedPadding], which can animate the padding instead of the |
888 | /// alignment. |
889 | /// * [AnimatedSlide], which can animate the translation of child by a given offset relative to its size. |
890 | /// * [AnimatedPositioned], which, as a child of a [Stack], automatically |
891 | /// transitions its child's position over a given duration whenever the given |
892 | /// position changes. |
893 | class AnimatedAlign extends ImplicitlyAnimatedWidget { |
894 | /// Creates a widget that positions its child by an alignment that animates |
895 | /// implicitly. |
896 | const AnimatedAlign({ |
897 | super.key, |
898 | required this.alignment, |
899 | this.child, |
900 | this.heightFactor, |
901 | this.widthFactor, |
902 | super.curve, |
903 | required super.duration, |
904 | super.onEnd, |
905 | }) : assert(widthFactor == null || widthFactor >= 0.0), |
906 | assert(heightFactor == null || heightFactor >= 0.0); |
907 | |
908 | /// How to align the child. |
909 | /// |
910 | /// The x and y values of the [Alignment] control the horizontal and vertical |
911 | /// alignment, respectively. An x value of -1.0 means that the left edge of |
912 | /// the child is aligned with the left edge of the parent whereas an x value |
913 | /// of 1.0 means that the right edge of the child is aligned with the right |
914 | /// edge of the parent. Other values interpolate (and extrapolate) linearly. |
915 | /// For example, a value of 0.0 means that the center of the child is aligned |
916 | /// with the center of the parent. |
917 | /// |
918 | /// See also: |
919 | /// |
920 | /// * [Alignment], which has more details and some convenience constants for |
921 | /// common positions. |
922 | /// * [AlignmentDirectional], which has a horizontal coordinate orientation |
923 | /// that depends on the [TextDirection]. |
924 | final AlignmentGeometry alignment; |
925 | |
926 | /// The widget below this widget in the tree. |
927 | /// |
928 | /// {@macro flutter.widgets.ProxyWidget.child} |
929 | final Widget? child; |
930 | |
931 | /// If non-null, sets its height to the child's height multiplied by this factor. |
932 | /// |
933 | /// Must be greater than or equal to 0.0, defaults to null. |
934 | final double? heightFactor; |
935 | |
936 | /// If non-null, sets its width to the child's width multiplied by this factor. |
937 | /// |
938 | /// Must be greater than or equal to 0.0, defaults to null. |
939 | final double? widthFactor; |
940 | |
941 | @override |
942 | AnimatedWidgetBaseState<AnimatedAlign> createState() => _AnimatedAlignState(); |
943 | |
944 | @override |
945 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
946 | super.debugFillProperties(properties); |
947 | properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment' , alignment)); |
948 | } |
949 | } |
950 | |
951 | class _AnimatedAlignState extends AnimatedWidgetBaseState<AnimatedAlign> { |
952 | AlignmentGeometryTween? _alignment; |
953 | Tween<double>? _heightFactorTween; |
954 | Tween<double>? _widthFactorTween; |
955 | |
956 | @override |
957 | void forEachTween(TweenVisitor<dynamic> visitor) { |
958 | _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?; |
959 | if (widget.heightFactor != null) { |
960 | _heightFactorTween = visitor(_heightFactorTween, widget.heightFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
961 | } |
962 | if (widget.widthFactor != null) { |
963 | _widthFactorTween = visitor(_widthFactorTween, widget.widthFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
964 | } |
965 | } |
966 | |
967 | @override |
968 | Widget build(BuildContext context) { |
969 | return Align( |
970 | alignment: _alignment!.evaluate(animation)!, |
971 | heightFactor: _heightFactorTween?.evaluate(animation), |
972 | widthFactor: _widthFactorTween?.evaluate(animation), |
973 | child: widget.child, |
974 | ); |
975 | } |
976 | |
977 | @override |
978 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
979 | super.debugFillProperties(description); |
980 | description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment' , _alignment, defaultValue: null)); |
981 | description.add(DiagnosticsProperty<Tween<double>>('widthFactor' , _widthFactorTween, defaultValue: null)); |
982 | description.add(DiagnosticsProperty<Tween<double>>('heightFactor' , _heightFactorTween, defaultValue: null)); |
983 | } |
984 | } |
985 | |
986 | /// Animated version of [Positioned] which automatically transitions the child's |
987 | /// position over a given duration whenever the given position changes. |
988 | /// |
989 | /// {@youtube 560 315 https://www.youtube.com/watch?v=hC3s2YdtWt8} |
990 | /// |
991 | /// Only works if it's the child of a [Stack]. |
992 | /// |
993 | /// This widget is a good choice if the _size_ of the child would end up |
994 | /// changing as a result of this animation. If the size is intended to remain |
995 | /// the same, with only the _position_ changing over time, then consider |
996 | /// [SlideTransition] instead. [SlideTransition] only triggers a repaint each |
997 | /// frame of the animation, whereas [AnimatedPositioned] will trigger a relayout |
998 | /// as well. |
999 | /// |
1000 | /// Here's an illustration of what using this widget looks like, using a [curve] |
1001 | /// of [Curves.fastOutSlowIn]. |
1002 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_positioned.mp4} |
1003 | /// |
1004 | /// For the animation, you can choose a [curve] as well as a [duration] and the |
1005 | /// widget will automatically animate to the new target position. If you require |
1006 | /// more control over the animation (e.g. if you want to stop it mid-animation), |
1007 | /// consider using a [PositionedTransition] instead, which takes a provided |
1008 | /// [Animation] as an argument. While that allows you to fine-tune the animation, |
1009 | /// it also requires more development overhead as you have to manually manage |
1010 | /// the lifecycle of the underlying [AnimationController]. |
1011 | /// |
1012 | /// {@tool dartpad} |
1013 | /// The following example transitions an AnimatedPositioned |
1014 | /// between two states. It adjusts the `height`, `width`, and |
1015 | /// [Positioned] properties when tapped. |
1016 | /// |
1017 | /// ** See code in examples/api/lib/widgets/implicit_animations/animated_positioned.0.dart ** |
1018 | /// {@end-tool} |
1019 | /// |
1020 | /// See also: |
1021 | /// |
1022 | /// * [AnimatedPositionedDirectional], which adapts to the ambient |
1023 | /// [Directionality] (the same as this widget, but for animating |
1024 | /// [PositionedDirectional]). |
1025 | class AnimatedPositioned extends ImplicitlyAnimatedWidget { |
1026 | /// Creates a widget that animates its position implicitly. |
1027 | /// |
1028 | /// Only two out of the three horizontal values ([left], [right], |
1029 | /// [width]), and only two out of the three vertical values ([top], |
1030 | /// [bottom], [height]), can be set. In each case, at least one of |
1031 | /// the three must be null. |
1032 | const AnimatedPositioned({ |
1033 | super.key, |
1034 | required this.child, |
1035 | this.left, |
1036 | this.top, |
1037 | this.right, |
1038 | this.bottom, |
1039 | this.width, |
1040 | this.height, |
1041 | super.curve, |
1042 | required super.duration, |
1043 | super.onEnd, |
1044 | }) : assert(left == null || right == null || width == null), |
1045 | assert(top == null || bottom == null || height == null); |
1046 | |
1047 | /// Creates a widget that animates the rectangle it occupies implicitly. |
1048 | AnimatedPositioned.fromRect({ |
1049 | super.key, |
1050 | required this.child, |
1051 | required Rect rect, |
1052 | super.curve, |
1053 | required super.duration, |
1054 | super.onEnd, |
1055 | }) : left = rect.left, |
1056 | top = rect.top, |
1057 | width = rect.width, |
1058 | height = rect.height, |
1059 | right = null, |
1060 | bottom = null; |
1061 | |
1062 | /// The widget below this widget in the tree. |
1063 | /// |
1064 | /// {@macro flutter.widgets.ProxyWidget.child} |
1065 | final Widget child; |
1066 | |
1067 | /// The offset of the child's left edge from the left of the stack. |
1068 | final double? left; |
1069 | |
1070 | /// The offset of the child's top edge from the top of the stack. |
1071 | final double? top; |
1072 | |
1073 | /// The offset of the child's right edge from the right of the stack. |
1074 | final double? right; |
1075 | |
1076 | /// The offset of the child's bottom edge from the bottom of the stack. |
1077 | final double? bottom; |
1078 | |
1079 | /// The child's width. |
1080 | /// |
1081 | /// Only two out of the three horizontal values ([left], [right], [width]) can |
1082 | /// be set. The third must be null. |
1083 | final double? width; |
1084 | |
1085 | /// The child's height. |
1086 | /// |
1087 | /// Only two out of the three vertical values ([top], [bottom], [height]) can |
1088 | /// be set. The third must be null. |
1089 | final double? height; |
1090 | |
1091 | @override |
1092 | AnimatedWidgetBaseState<AnimatedPositioned> createState() => _AnimatedPositionedState(); |
1093 | |
1094 | @override |
1095 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1096 | super.debugFillProperties(properties); |
1097 | properties.add(DoubleProperty('left' , left, defaultValue: null)); |
1098 | properties.add(DoubleProperty('top' , top, defaultValue: null)); |
1099 | properties.add(DoubleProperty('right' , right, defaultValue: null)); |
1100 | properties.add(DoubleProperty('bottom' , bottom, defaultValue: null)); |
1101 | properties.add(DoubleProperty('width' , width, defaultValue: null)); |
1102 | properties.add(DoubleProperty('height' , height, defaultValue: null)); |
1103 | } |
1104 | } |
1105 | |
1106 | class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositioned> { |
1107 | Tween<double>? _left; |
1108 | Tween<double>? _top; |
1109 | Tween<double>? _right; |
1110 | Tween<double>? _bottom; |
1111 | Tween<double>? _width; |
1112 | Tween<double>? _height; |
1113 | |
1114 | @override |
1115 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1116 | _left = visitor(_left, widget.left, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1117 | _top = visitor(_top, widget.top, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1118 | _right = visitor(_right, widget.right, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1119 | _bottom = visitor(_bottom, widget.bottom, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1120 | _width = visitor(_width, widget.width, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1121 | _height = visitor(_height, widget.height, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1122 | } |
1123 | |
1124 | @override |
1125 | Widget build(BuildContext context) { |
1126 | return Positioned( |
1127 | left: _left?.evaluate(animation), |
1128 | top: _top?.evaluate(animation), |
1129 | right: _right?.evaluate(animation), |
1130 | bottom: _bottom?.evaluate(animation), |
1131 | width: _width?.evaluate(animation), |
1132 | height: _height?.evaluate(animation), |
1133 | child: widget.child, |
1134 | ); |
1135 | } |
1136 | |
1137 | @override |
1138 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
1139 | super.debugFillProperties(description); |
1140 | description.add(ObjectFlagProperty<Tween<double>>.has('left' , _left)); |
1141 | description.add(ObjectFlagProperty<Tween<double>>.has('top' , _top)); |
1142 | description.add(ObjectFlagProperty<Tween<double>>.has('right' , _right)); |
1143 | description.add(ObjectFlagProperty<Tween<double>>.has('bottom' , _bottom)); |
1144 | description.add(ObjectFlagProperty<Tween<double>>.has('width' , _width)); |
1145 | description.add(ObjectFlagProperty<Tween<double>>.has('height' , _height)); |
1146 | } |
1147 | } |
1148 | |
1149 | /// Animated version of [PositionedDirectional] which automatically transitions |
1150 | /// the child's position over a given duration whenever the given position |
1151 | /// changes. |
1152 | /// |
1153 | /// The ambient [Directionality] is used to determine whether [start] is to the |
1154 | /// left or to the right. |
1155 | /// |
1156 | /// Only works if it's the child of a [Stack]. |
1157 | /// |
1158 | /// This widget is a good choice if the _size_ of the child would end up |
1159 | /// changing as a result of this animation. If the size is intended to remain |
1160 | /// the same, with only the _position_ changing over time, then consider |
1161 | /// [SlideTransition] instead. [SlideTransition] only triggers a repaint each |
1162 | /// frame of the animation, whereas [AnimatedPositionedDirectional] will trigger |
1163 | /// a relayout as well. ([SlideTransition] is also text-direction-aware.) |
1164 | /// |
1165 | /// Here's an illustration of what using this widget looks like, using a [curve] |
1166 | /// of [Curves.fastOutSlowIn]. |
1167 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_positioned_directional.mp4} |
1168 | /// |
1169 | /// See also: |
1170 | /// |
1171 | /// * [AnimatedPositioned], which specifies the widget's position visually (the |
1172 | /// same as this widget, but for animating [Positioned]). |
1173 | class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget { |
1174 | /// Creates a widget that animates its position implicitly. |
1175 | /// |
1176 | /// Only two out of the three horizontal values ([start], [end], [width]), and |
1177 | /// only two out of the three vertical values ([top], [bottom], [height]), can |
1178 | /// be set. In each case, at least one of the three must be null. |
1179 | const AnimatedPositionedDirectional({ |
1180 | super.key, |
1181 | required this.child, |
1182 | this.start, |
1183 | this.top, |
1184 | this.end, |
1185 | this.bottom, |
1186 | this.width, |
1187 | this.height, |
1188 | super.curve, |
1189 | required super.duration, |
1190 | super.onEnd, |
1191 | }) : assert(start == null || end == null || width == null), |
1192 | assert(top == null || bottom == null || height == null); |
1193 | |
1194 | /// The widget below this widget in the tree. |
1195 | /// |
1196 | /// {@macro flutter.widgets.ProxyWidget.child} |
1197 | final Widget child; |
1198 | |
1199 | /// The offset of the child's start edge from the start of the stack. |
1200 | final double? start; |
1201 | |
1202 | /// The offset of the child's top edge from the top of the stack. |
1203 | final double? top; |
1204 | |
1205 | /// The offset of the child's end edge from the end of the stack. |
1206 | final double? end; |
1207 | |
1208 | /// The offset of the child's bottom edge from the bottom of the stack. |
1209 | final double? bottom; |
1210 | |
1211 | /// The child's width. |
1212 | /// |
1213 | /// Only two out of the three horizontal values ([start], [end], [width]) can |
1214 | /// be set. The third must be null. |
1215 | final double? width; |
1216 | |
1217 | /// The child's height. |
1218 | /// |
1219 | /// Only two out of the three vertical values ([top], [bottom], [height]) can |
1220 | /// be set. The third must be null. |
1221 | final double? height; |
1222 | |
1223 | @override |
1224 | AnimatedWidgetBaseState<AnimatedPositionedDirectional> createState() => _AnimatedPositionedDirectionalState(); |
1225 | |
1226 | @override |
1227 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1228 | super.debugFillProperties(properties); |
1229 | properties.add(DoubleProperty('start' , start, defaultValue: null)); |
1230 | properties.add(DoubleProperty('top' , top, defaultValue: null)); |
1231 | properties.add(DoubleProperty('end' , end, defaultValue: null)); |
1232 | properties.add(DoubleProperty('bottom' , bottom, defaultValue: null)); |
1233 | properties.add(DoubleProperty('width' , width, defaultValue: null)); |
1234 | properties.add(DoubleProperty('height' , height, defaultValue: null)); |
1235 | } |
1236 | } |
1237 | |
1238 | class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<AnimatedPositionedDirectional> { |
1239 | Tween<double>? _start; |
1240 | Tween<double>? _top; |
1241 | Tween<double>? _end; |
1242 | Tween<double>? _bottom; |
1243 | Tween<double>? _width; |
1244 | Tween<double>? _height; |
1245 | |
1246 | @override |
1247 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1248 | _start = visitor(_start, widget.start, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1249 | _top = visitor(_top, widget.top, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1250 | _end = visitor(_end, widget.end, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1251 | _bottom = visitor(_bottom, widget.bottom, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1252 | _width = visitor(_width, widget.width, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1253 | _height = visitor(_height, widget.height, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1254 | } |
1255 | |
1256 | @override |
1257 | Widget build(BuildContext context) { |
1258 | assert(debugCheckHasDirectionality(context)); |
1259 | return Positioned.directional( |
1260 | textDirection: Directionality.of(context), |
1261 | start: _start?.evaluate(animation), |
1262 | top: _top?.evaluate(animation), |
1263 | end: _end?.evaluate(animation), |
1264 | bottom: _bottom?.evaluate(animation), |
1265 | width: _width?.evaluate(animation), |
1266 | height: _height?.evaluate(animation), |
1267 | child: widget.child, |
1268 | ); |
1269 | } |
1270 | |
1271 | @override |
1272 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
1273 | super.debugFillProperties(description); |
1274 | description.add(ObjectFlagProperty<Tween<double>>.has('start' , _start)); |
1275 | description.add(ObjectFlagProperty<Tween<double>>.has('top' , _top)); |
1276 | description.add(ObjectFlagProperty<Tween<double>>.has('end' , _end)); |
1277 | description.add(ObjectFlagProperty<Tween<double>>.has('bottom' , _bottom)); |
1278 | description.add(ObjectFlagProperty<Tween<double>>.has('width' , _width)); |
1279 | description.add(ObjectFlagProperty<Tween<double>>.has('height' , _height)); |
1280 | } |
1281 | } |
1282 | |
1283 | /// Animated version of [Transform.scale] which automatically transitions the child's |
1284 | /// scale over a given duration whenever the given scale changes. |
1285 | /// |
1286 | /// {@tool snippet} |
1287 | /// |
1288 | /// This code defines a widget that uses [AnimatedScale] to change the size |
1289 | /// of [FlutterLogo] gradually to a new scale whenever the button is pressed. |
1290 | /// |
1291 | /// ```dart |
1292 | /// class LogoScale extends StatefulWidget { |
1293 | /// const LogoScale({super.key}); |
1294 | /// |
1295 | /// @override |
1296 | /// State<LogoScale> createState() => LogoScaleState(); |
1297 | /// } |
1298 | /// |
1299 | /// class LogoScaleState extends State<LogoScale> { |
1300 | /// double scale = 1.0; |
1301 | /// |
1302 | /// void _changeScale() { |
1303 | /// setState(() => scale = scale == 1.0 ? 3.0 : 1.0); |
1304 | /// } |
1305 | /// |
1306 | /// @override |
1307 | /// Widget build(BuildContext context) { |
1308 | /// return Column( |
1309 | /// mainAxisAlignment: MainAxisAlignment.center, |
1310 | /// children: <Widget>[ |
1311 | /// ElevatedButton( |
1312 | /// onPressed: _changeScale, |
1313 | /// child: const Text('Scale Logo'), |
1314 | /// ), |
1315 | /// Padding( |
1316 | /// padding: const EdgeInsets.all(50), |
1317 | /// child: AnimatedScale( |
1318 | /// scale: scale, |
1319 | /// duration: const Duration(seconds: 2), |
1320 | /// child: const FlutterLogo(), |
1321 | /// ), |
1322 | /// ), |
1323 | /// ], |
1324 | /// ); |
1325 | /// } |
1326 | /// } |
1327 | /// ``` |
1328 | /// {@end-tool} |
1329 | /// |
1330 | /// See also: |
1331 | /// |
1332 | /// * [AnimatedRotation], for animating the rotation of a child. |
1333 | /// * [AnimatedSize], for animating the resize of a child based on changes |
1334 | /// in layout. |
1335 | /// * [AnimatedSlide], for animating the translation of a child by a given offset relative to its size. |
1336 | /// * [ScaleTransition], an explicitly animated version of this widget, where |
1337 | /// an [Animation] is provided by the caller instead of being built in. |
1338 | class AnimatedScale extends ImplicitlyAnimatedWidget { |
1339 | /// Creates a widget that animates its scale implicitly. |
1340 | const AnimatedScale({ |
1341 | super.key, |
1342 | this.child, |
1343 | required this.scale, |
1344 | this.alignment = Alignment.center, |
1345 | this.filterQuality, |
1346 | super.curve, |
1347 | required super.duration, |
1348 | super.onEnd, |
1349 | }); |
1350 | |
1351 | /// The widget below this widget in the tree. |
1352 | /// |
1353 | /// {@macro flutter.widgets.ProxyWidget.child} |
1354 | final Widget? child; |
1355 | |
1356 | /// The target scale. |
1357 | final double scale; |
1358 | |
1359 | /// The alignment of the origin of the coordinate system in which the scale |
1360 | /// takes place, relative to the size of the box. |
1361 | /// |
1362 | /// For example, to set the origin of the scale to bottom middle, you can use |
1363 | /// an alignment of (0.0, 1.0). |
1364 | final Alignment alignment; |
1365 | |
1366 | /// The filter quality with which to apply the transform as a bitmap operation. |
1367 | /// |
1368 | /// {@macro flutter.widgets.Transform.optional.FilterQuality} |
1369 | final FilterQuality? filterQuality; |
1370 | |
1371 | @override |
1372 | ImplicitlyAnimatedWidgetState<AnimatedScale> createState() => _AnimatedScaleState(); |
1373 | |
1374 | @override |
1375 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1376 | super.debugFillProperties(properties); |
1377 | properties.add(DoubleProperty('scale' , scale)); |
1378 | properties.add(DiagnosticsProperty<Alignment>('alignment' , alignment, defaultValue: Alignment.center)); |
1379 | properties.add(EnumProperty<FilterQuality>('filterQuality' , filterQuality, defaultValue: null)); |
1380 | } |
1381 | } |
1382 | |
1383 | class _AnimatedScaleState extends ImplicitlyAnimatedWidgetState<AnimatedScale> { |
1384 | Tween<double>? _scale; |
1385 | late Animation<double> _scaleAnimation; |
1386 | |
1387 | @override |
1388 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1389 | _scale = visitor(_scale, widget.scale, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1390 | } |
1391 | |
1392 | @override |
1393 | void didUpdateTweens() { |
1394 | _scaleAnimation = animation.drive(_scale!); |
1395 | } |
1396 | |
1397 | @override |
1398 | Widget build(BuildContext context) { |
1399 | return ScaleTransition( |
1400 | scale: _scaleAnimation, |
1401 | alignment: widget.alignment, |
1402 | filterQuality: widget.filterQuality, |
1403 | child: widget.child, |
1404 | ); |
1405 | } |
1406 | } |
1407 | |
1408 | /// Animated version of [Transform.rotate] which automatically transitions the child's |
1409 | /// rotation over a given duration whenever the given rotation changes. |
1410 | /// |
1411 | /// {@tool snippet} |
1412 | /// |
1413 | /// This code defines a widget that uses [AnimatedRotation] to rotate a [FlutterLogo] |
1414 | /// gradually by an eighth of a turn (45 degrees) with each press of the button. |
1415 | /// |
1416 | /// ```dart |
1417 | /// class LogoRotate extends StatefulWidget { |
1418 | /// const LogoRotate({super.key}); |
1419 | /// |
1420 | /// @override |
1421 | /// State<LogoRotate> createState() => LogoRotateState(); |
1422 | /// } |
1423 | /// |
1424 | /// class LogoRotateState extends State<LogoRotate> { |
1425 | /// double turns = 0.0; |
1426 | /// |
1427 | /// void _changeRotation() { |
1428 | /// setState(() => turns += 1.0 / 8.0); |
1429 | /// } |
1430 | /// |
1431 | /// @override |
1432 | /// Widget build(BuildContext context) { |
1433 | /// return Column( |
1434 | /// mainAxisAlignment: MainAxisAlignment.center, |
1435 | /// children: <Widget>[ |
1436 | /// ElevatedButton( |
1437 | /// onPressed: _changeRotation, |
1438 | /// child: const Text('Rotate Logo'), |
1439 | /// ), |
1440 | /// Padding( |
1441 | /// padding: const EdgeInsets.all(50), |
1442 | /// child: AnimatedRotation( |
1443 | /// turns: turns, |
1444 | /// duration: const Duration(seconds: 1), |
1445 | /// child: const FlutterLogo(), |
1446 | /// ), |
1447 | /// ), |
1448 | /// ], |
1449 | /// ); |
1450 | /// } |
1451 | /// } |
1452 | /// ``` |
1453 | /// {@end-tool} |
1454 | /// |
1455 | /// See also: |
1456 | /// |
1457 | /// * [AnimatedScale], for animating the scale of a child. |
1458 | /// * [RotationTransition], an explicitly animated version of this widget, where |
1459 | /// an [Animation] is provided by the caller instead of being built in. |
1460 | class AnimatedRotation extends ImplicitlyAnimatedWidget { |
1461 | /// Creates a widget that animates its rotation implicitly. |
1462 | const AnimatedRotation({ |
1463 | super.key, |
1464 | this.child, |
1465 | required this.turns, |
1466 | this.alignment = Alignment.center, |
1467 | this.filterQuality, |
1468 | super.curve, |
1469 | required super.duration, |
1470 | super.onEnd, |
1471 | }); |
1472 | |
1473 | /// The widget below this widget in the tree. |
1474 | /// |
1475 | /// {@macro flutter.widgets.ProxyWidget.child} |
1476 | final Widget? child; |
1477 | |
1478 | /// The animation that controls the rotation of the child. |
1479 | /// |
1480 | /// If the current value of the turns animation is v, the child will be |
1481 | /// rotated v * 2 * pi radians before being painted. |
1482 | final double turns; |
1483 | |
1484 | /// The alignment of the origin of the coordinate system in which the rotation |
1485 | /// takes place, relative to the size of the box. |
1486 | /// |
1487 | /// For example, to set the origin of the rotation to bottom middle, you can use |
1488 | /// an alignment of (0.0, 1.0). |
1489 | final Alignment alignment; |
1490 | |
1491 | /// The filter quality with which to apply the transform as a bitmap operation. |
1492 | /// |
1493 | /// {@macro flutter.widgets.Transform.optional.FilterQuality} |
1494 | final FilterQuality? filterQuality; |
1495 | |
1496 | @override |
1497 | ImplicitlyAnimatedWidgetState<AnimatedRotation> createState() => _AnimatedRotationState(); |
1498 | |
1499 | @override |
1500 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1501 | super.debugFillProperties(properties); |
1502 | properties.add(DoubleProperty('turns' , turns)); |
1503 | properties.add(DiagnosticsProperty<Alignment>('alignment' , alignment, defaultValue: Alignment.center)); |
1504 | properties.add(EnumProperty<FilterQuality>('filterQuality' , filterQuality, defaultValue: null)); |
1505 | } |
1506 | } |
1507 | |
1508 | class _AnimatedRotationState extends ImplicitlyAnimatedWidgetState<AnimatedRotation> { |
1509 | Tween<double>? _turns; |
1510 | late Animation<double> _turnsAnimation; |
1511 | |
1512 | @override |
1513 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1514 | _turns = visitor(_turns, widget.turns, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1515 | } |
1516 | |
1517 | @override |
1518 | void didUpdateTweens() { |
1519 | _turnsAnimation = animation.drive(_turns!); |
1520 | } |
1521 | |
1522 | @override |
1523 | Widget build(BuildContext context) { |
1524 | return RotationTransition( |
1525 | turns: _turnsAnimation, |
1526 | alignment: widget.alignment, |
1527 | filterQuality: widget.filterQuality, |
1528 | child: widget.child, |
1529 | ); |
1530 | } |
1531 | } |
1532 | |
1533 | /// Widget which automatically transitions the child's |
1534 | /// offset relative to its normal position whenever the given offset changes. |
1535 | /// |
1536 | /// The translation is expressed as an [Offset] scaled to the child's size. For |
1537 | /// example, an [Offset] with a `dx` of 0.25 will result in a horizontal |
1538 | /// translation of one quarter the width of the child. |
1539 | /// |
1540 | /// {@tool dartpad} |
1541 | /// This code defines a widget that uses [AnimatedSlide] to translate a [FlutterLogo] |
1542 | /// up or down and right or left by dragging the X and Y axis sliders. |
1543 | /// |
1544 | /// ** See code in examples/api/lib/widgets/implicit_animations/animated_slide.0.dart ** |
1545 | /// {@end-tool} |
1546 | /// |
1547 | /// See also: |
1548 | /// |
1549 | /// * [AnimatedPositioned], which, as a child of a [Stack], automatically |
1550 | /// transitions its child's position over a given duration whenever the given |
1551 | /// position changes. |
1552 | /// * [AnimatedAlign], which automatically transitions its child's |
1553 | /// position over a given duration whenever the given [AnimatedAlign.alignment] changes. |
1554 | class AnimatedSlide extends ImplicitlyAnimatedWidget { |
1555 | /// Creates a widget that animates its offset translation implicitly. |
1556 | const AnimatedSlide({ |
1557 | super.key, |
1558 | this.child, |
1559 | required this.offset, |
1560 | super.curve, |
1561 | required super.duration, |
1562 | super.onEnd, |
1563 | }); |
1564 | |
1565 | /// The widget below this widget in the tree. |
1566 | /// |
1567 | /// {@macro flutter.widgets.ProxyWidget.child} |
1568 | final Widget? child; |
1569 | |
1570 | /// The target offset. |
1571 | /// The child will be translated horizontally by `width * dx` and vertically by `height * dy` |
1572 | final Offset offset; |
1573 | |
1574 | @override |
1575 | ImplicitlyAnimatedWidgetState<AnimatedSlide> createState() => _AnimatedSlideState(); |
1576 | |
1577 | @override |
1578 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1579 | super.debugFillProperties(properties); |
1580 | properties.add(DiagnosticsProperty<Offset>('offset' , offset)); |
1581 | } |
1582 | } |
1583 | |
1584 | class _AnimatedSlideState extends ImplicitlyAnimatedWidgetState<AnimatedSlide> { |
1585 | Tween<Offset>? _offset; |
1586 | late Animation<Offset> _offsetAnimation; |
1587 | |
1588 | @override |
1589 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1590 | _offset = visitor(_offset, widget.offset, (dynamic value) => Tween<Offset>(begin: value as Offset)) as Tween<Offset>?; |
1591 | } |
1592 | |
1593 | @override |
1594 | void didUpdateTweens() { |
1595 | _offsetAnimation = animation.drive(_offset!); |
1596 | } |
1597 | |
1598 | @override |
1599 | Widget build(BuildContext context) { |
1600 | return SlideTransition( |
1601 | position: _offsetAnimation, |
1602 | child: widget.child, |
1603 | ); |
1604 | } |
1605 | } |
1606 | |
1607 | /// Animated version of [Opacity] which automatically transitions the child's |
1608 | /// opacity over a given duration whenever the given opacity changes. |
1609 | /// |
1610 | /// {@youtube 560 315 https://www.youtube.com/watch?v=QZAvjqOqiLY} |
1611 | /// |
1612 | /// Animating an opacity is relatively expensive because it requires painting |
1613 | /// the child into an intermediate buffer. |
1614 | /// |
1615 | /// Here's an illustration of what using this widget looks like, using a [curve] |
1616 | /// of [Curves.fastOutSlowIn]. |
1617 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_opacity.mp4} |
1618 | /// |
1619 | /// {@tool snippet} |
1620 | /// |
1621 | /// ```dart |
1622 | /// class LogoFade extends StatefulWidget { |
1623 | /// const LogoFade({super.key}); |
1624 | /// |
1625 | /// @override |
1626 | /// State<LogoFade> createState() => LogoFadeState(); |
1627 | /// } |
1628 | /// |
1629 | /// class LogoFadeState extends State<LogoFade> { |
1630 | /// double opacityLevel = 1.0; |
1631 | /// |
1632 | /// void _changeOpacity() { |
1633 | /// setState(() => opacityLevel = opacityLevel == 0 ? 1.0 : 0.0); |
1634 | /// } |
1635 | /// |
1636 | /// @override |
1637 | /// Widget build(BuildContext context) { |
1638 | /// return Column( |
1639 | /// mainAxisAlignment: MainAxisAlignment.center, |
1640 | /// children: <Widget>[ |
1641 | /// AnimatedOpacity( |
1642 | /// opacity: opacityLevel, |
1643 | /// duration: const Duration(seconds: 3), |
1644 | /// child: const FlutterLogo(), |
1645 | /// ), |
1646 | /// ElevatedButton( |
1647 | /// onPressed: _changeOpacity, |
1648 | /// child: const Text('Fade Logo'), |
1649 | /// ), |
1650 | /// ], |
1651 | /// ); |
1652 | /// } |
1653 | /// } |
1654 | /// ``` |
1655 | /// {@end-tool} |
1656 | /// |
1657 | /// ## Hit testing |
1658 | /// |
1659 | /// Setting the [opacity] to zero does not prevent hit testing from being |
1660 | /// applied to the descendants of the [AnimatedOpacity] widget. This can be |
1661 | /// confusing for the user, who may not see anything, and may believe the area |
1662 | /// of the interface where the [AnimatedOpacity] is hiding a widget to be |
1663 | /// non-interactive. |
1664 | /// |
1665 | /// With certain widgets, such as [Flow], that compute their positions only when |
1666 | /// they are painted, this can actually lead to bugs (from unexpected geometry |
1667 | /// to exceptions), because those widgets are not painted by the [AnimatedOpacity] |
1668 | /// widget at all when the [opacity] animation reaches zero. |
1669 | /// |
1670 | /// To avoid such problems, it is generally a good idea to use an |
1671 | /// [IgnorePointer] widget when setting the [opacity] to zero. This prevents |
1672 | /// interactions with any children in the subtree when the [child] is animating |
1673 | /// away. |
1674 | /// |
1675 | /// See also: |
1676 | /// |
1677 | /// * [AnimatedCrossFade], for fading between two children. |
1678 | /// * [AnimatedSwitcher], for fading between many children in sequence. |
1679 | /// * [FadeTransition], an explicitly animated version of this widget, where |
1680 | /// an [Animation] is provided by the caller instead of being built in. |
1681 | /// * [SliverAnimatedOpacity], for automatically transitioning a _sliver's_ |
1682 | /// opacity over a given duration whenever the given opacity changes. |
1683 | class AnimatedOpacity extends ImplicitlyAnimatedWidget { |
1684 | /// Creates a widget that animates its opacity implicitly. |
1685 | /// |
1686 | /// The [opacity] argument must be between zero and one, inclusive. |
1687 | const AnimatedOpacity({ |
1688 | super.key, |
1689 | this.child, |
1690 | required this.opacity, |
1691 | super.curve, |
1692 | required super.duration, |
1693 | super.onEnd, |
1694 | this.alwaysIncludeSemantics = false, |
1695 | }) : assert(opacity >= 0.0 && opacity <= 1.0); |
1696 | |
1697 | /// The widget below this widget in the tree. |
1698 | /// |
1699 | /// {@macro flutter.widgets.ProxyWidget.child} |
1700 | final Widget? child; |
1701 | |
1702 | /// The target opacity. |
1703 | /// |
1704 | /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent |
1705 | /// (i.e., invisible). |
1706 | final double opacity; |
1707 | |
1708 | /// Whether the semantic information of the children is always included. |
1709 | /// |
1710 | /// Defaults to false. |
1711 | /// |
1712 | /// When true, regardless of the opacity settings the child semantic |
1713 | /// information is exposed as if the widget were fully visible. This is |
1714 | /// useful in cases where labels may be hidden during animations that |
1715 | /// would otherwise contribute relevant semantics. |
1716 | final bool alwaysIncludeSemantics; |
1717 | |
1718 | @override |
1719 | ImplicitlyAnimatedWidgetState<AnimatedOpacity> createState() => _AnimatedOpacityState(); |
1720 | |
1721 | @override |
1722 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1723 | super.debugFillProperties(properties); |
1724 | properties.add(DoubleProperty('opacity' , opacity)); |
1725 | } |
1726 | } |
1727 | |
1728 | class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacity> { |
1729 | Tween<double>? _opacity; |
1730 | late Animation<double> _opacityAnimation; |
1731 | |
1732 | @override |
1733 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1734 | _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1735 | } |
1736 | |
1737 | @override |
1738 | void didUpdateTweens() { |
1739 | _opacityAnimation = animation.drive(_opacity!); |
1740 | } |
1741 | |
1742 | @override |
1743 | Widget build(BuildContext context) { |
1744 | return FadeTransition( |
1745 | opacity: _opacityAnimation, |
1746 | alwaysIncludeSemantics: widget.alwaysIncludeSemantics, |
1747 | child: widget.child, |
1748 | ); |
1749 | } |
1750 | } |
1751 | |
1752 | /// Animated version of [SliverOpacity] which automatically transitions the |
1753 | /// sliver child's opacity over a given duration whenever the given opacity |
1754 | /// changes. |
1755 | /// |
1756 | /// Animating an opacity is relatively expensive because it requires painting |
1757 | /// the sliver child into an intermediate buffer. |
1758 | /// |
1759 | /// Here's an illustration of what using this widget looks like, using a [curve] |
1760 | /// of [Curves.fastOutSlowIn]. |
1761 | /// |
1762 | /// {@tool dartpad} |
1763 | /// Creates a [CustomScrollView] with a [SliverFixedExtentList] and a |
1764 | /// [FloatingActionButton]. Pressing the button animates the lists' opacity. |
1765 | /// |
1766 | /// ** See code in examples/api/lib/widgets/implicit_animations/sliver_animated_opacity.0.dart ** |
1767 | /// {@end-tool} |
1768 | /// |
1769 | /// ## Hit testing |
1770 | /// |
1771 | /// Setting the [opacity] to zero does not prevent hit testing from being |
1772 | /// applied to the descendants of the [SliverAnimatedOpacity] widget. This can |
1773 | /// be confusing for the user, who may not see anything, and may believe the |
1774 | /// area of the interface where the [SliverAnimatedOpacity] is hiding a widget |
1775 | /// to be non-interactive. |
1776 | /// |
1777 | /// With certain widgets, such as [Flow], that compute their positions only when |
1778 | /// they are painted, this can actually lead to bugs (from unexpected geometry |
1779 | /// to exceptions), because those widgets are not painted by the |
1780 | /// [SliverAnimatedOpacity] widget at all when the [opacity] animation reaches |
1781 | /// zero. |
1782 | /// |
1783 | /// To avoid such problems, it is generally a good idea to use a |
1784 | /// [SliverIgnorePointer] widget when setting the [opacity] to zero. This |
1785 | /// prevents interactions with any children in the subtree when the [sliver] is |
1786 | /// animating away. |
1787 | /// |
1788 | /// See also: |
1789 | /// |
1790 | /// * [SliverFadeTransition], an explicitly animated version of this widget, where |
1791 | /// an [Animation] is provided by the caller instead of being built in. |
1792 | /// * [AnimatedOpacity], for automatically transitioning a box child's |
1793 | /// opacity over a given duration whenever the given opacity changes. |
1794 | class SliverAnimatedOpacity extends ImplicitlyAnimatedWidget { |
1795 | /// Creates a widget that animates its opacity implicitly. |
1796 | /// |
1797 | /// The [opacity] argument must be between zero and one, inclusive. |
1798 | const SliverAnimatedOpacity({ |
1799 | super.key, |
1800 | this.sliver, |
1801 | required this.opacity, |
1802 | super.curve, |
1803 | required super.duration, |
1804 | super.onEnd, |
1805 | this.alwaysIncludeSemantics = false, |
1806 | }) : assert(opacity >= 0.0 && opacity <= 1.0); |
1807 | |
1808 | /// The sliver below this widget in the tree. |
1809 | final Widget? sliver; |
1810 | |
1811 | /// The target opacity. |
1812 | /// |
1813 | /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent |
1814 | /// (i.e., invisible). |
1815 | final double opacity; |
1816 | |
1817 | /// Whether the semantic information of the children is always included. |
1818 | /// |
1819 | /// Defaults to false. |
1820 | /// |
1821 | /// When true, regardless of the opacity settings the sliver child's semantic |
1822 | /// information is exposed as if the widget were fully visible. This is |
1823 | /// useful in cases where labels may be hidden during animations that |
1824 | /// would otherwise contribute relevant semantics. |
1825 | final bool alwaysIncludeSemantics; |
1826 | |
1827 | @override |
1828 | ImplicitlyAnimatedWidgetState<SliverAnimatedOpacity> createState() => _SliverAnimatedOpacityState(); |
1829 | |
1830 | @override |
1831 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1832 | super.debugFillProperties(properties); |
1833 | properties.add(DoubleProperty('opacity' , opacity)); |
1834 | } |
1835 | } |
1836 | |
1837 | class _SliverAnimatedOpacityState extends ImplicitlyAnimatedWidgetState<SliverAnimatedOpacity> { |
1838 | Tween<double>? _opacity; |
1839 | late Animation<double> _opacityAnimation; |
1840 | |
1841 | @override |
1842 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1843 | _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
1844 | } |
1845 | |
1846 | @override |
1847 | void didUpdateTweens() { |
1848 | _opacityAnimation = animation.drive(_opacity!); |
1849 | } |
1850 | |
1851 | @override |
1852 | Widget build(BuildContext context) { |
1853 | return SliverFadeTransition( |
1854 | opacity: _opacityAnimation, |
1855 | sliver: widget.sliver, |
1856 | alwaysIncludeSemantics: widget.alwaysIncludeSemantics, |
1857 | ); |
1858 | } |
1859 | } |
1860 | |
1861 | /// Animated version of [DefaultTextStyle] which automatically transitions the |
1862 | /// default text style (the text style to apply to descendant [Text] widgets |
1863 | /// without explicit style) over a given duration whenever the given style |
1864 | /// changes. |
1865 | /// |
1866 | /// The [textAlign], [softWrap], [overflow], [maxLines], [textWidthBasis] |
1867 | /// and [textHeightBehavior] properties are not animated and take effect |
1868 | /// immediately when changed. |
1869 | /// |
1870 | /// Here's an illustration of what using this widget looks like, using a [curve] |
1871 | /// of [Curves.elasticInOut]. |
1872 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_default_text_style.mp4} |
1873 | /// |
1874 | /// For the animation, you can choose a [curve] as well as a [duration] and the |
1875 | /// widget will automatically animate to the new default text style. If you require |
1876 | /// more control over the animation (e.g. if you want to stop it mid-animation), |
1877 | /// consider using a [DefaultTextStyleTransition] instead, which takes a provided |
1878 | /// [Animation] as argument. While that allows you to fine-tune the animation, |
1879 | /// it also requires more development overhead as you have to manually manage |
1880 | /// the lifecycle of the underlying [AnimationController]. |
1881 | class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget { |
1882 | /// Creates a widget that animates the default text style implicitly. |
1883 | const AnimatedDefaultTextStyle({ |
1884 | super.key, |
1885 | required this.child, |
1886 | required this.style, |
1887 | this.textAlign, |
1888 | this.softWrap = true, |
1889 | this.overflow = TextOverflow.clip, |
1890 | this.maxLines, |
1891 | this.textWidthBasis = TextWidthBasis.parent, |
1892 | this.textHeightBehavior, |
1893 | super.curve, |
1894 | required super.duration, |
1895 | super.onEnd, |
1896 | }) : assert(maxLines == null || maxLines > 0); |
1897 | |
1898 | /// The widget below this widget in the tree. |
1899 | /// |
1900 | /// {@macro flutter.widgets.ProxyWidget.child} |
1901 | final Widget child; |
1902 | |
1903 | /// The target text style. |
1904 | /// |
1905 | /// When this property is changed, the style will be animated over [duration] time. |
1906 | final TextStyle style; |
1907 | |
1908 | /// How the text should be aligned horizontally. |
1909 | /// |
1910 | /// This property takes effect immediately when changed, it is not animated. |
1911 | final TextAlign? textAlign; |
1912 | |
1913 | /// Whether the text should break at soft line breaks. |
1914 | /// |
1915 | /// This property takes effect immediately when changed, it is not animated. |
1916 | /// |
1917 | /// See [DefaultTextStyle.softWrap] for more details. |
1918 | final bool softWrap; |
1919 | |
1920 | /// How visual overflow should be handled. |
1921 | /// |
1922 | /// This property takes effect immediately when changed, it is not animated. |
1923 | final TextOverflow overflow; |
1924 | |
1925 | /// An optional maximum number of lines for the text to span, wrapping if necessary. |
1926 | /// |
1927 | /// This property takes effect immediately when changed, it is not animated. |
1928 | /// |
1929 | /// See [DefaultTextStyle.maxLines] for more details. |
1930 | final int? maxLines; |
1931 | |
1932 | /// The strategy to use when calculating the width of the Text. |
1933 | /// |
1934 | /// See [TextWidthBasis] for possible values and their implications. |
1935 | final TextWidthBasis textWidthBasis; |
1936 | |
1937 | /// {@macro dart.ui.textHeightBehavior} |
1938 | final ui.TextHeightBehavior? textHeightBehavior; |
1939 | |
1940 | @override |
1941 | AnimatedWidgetBaseState<AnimatedDefaultTextStyle> createState() => _AnimatedDefaultTextStyleState(); |
1942 | |
1943 | @override |
1944 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1945 | super.debugFillProperties(properties); |
1946 | style.debugFillProperties(properties); |
1947 | properties.add(EnumProperty<TextAlign>('textAlign' , textAlign, defaultValue: null)); |
1948 | properties.add(FlagProperty('softWrap' , value: softWrap, ifTrue: 'wrapping at box width' , ifFalse: 'no wrapping except at line break characters' , showName: true)); |
1949 | properties.add(EnumProperty<TextOverflow>('overflow' , overflow, defaultValue: null)); |
1950 | properties.add(IntProperty('maxLines' , maxLines, defaultValue: null)); |
1951 | properties.add(EnumProperty<TextWidthBasis>('textWidthBasis' , textWidthBasis, defaultValue: TextWidthBasis.parent)); |
1952 | properties.add(DiagnosticsProperty<ui.TextHeightBehavior>('textHeightBehavior' , textHeightBehavior, defaultValue: null)); |
1953 | } |
1954 | } |
1955 | |
1956 | class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDefaultTextStyle> { |
1957 | TextStyleTween? _style; |
1958 | |
1959 | @override |
1960 | void forEachTween(TweenVisitor<dynamic> visitor) { |
1961 | _style = visitor(_style, widget.style, (dynamic value) => TextStyleTween(begin: value as TextStyle)) as TextStyleTween?; |
1962 | } |
1963 | |
1964 | @override |
1965 | Widget build(BuildContext context) { |
1966 | return DefaultTextStyle( |
1967 | style: _style!.evaluate(animation), |
1968 | textAlign: widget.textAlign, |
1969 | softWrap: widget.softWrap, |
1970 | overflow: widget.overflow, |
1971 | maxLines: widget.maxLines, |
1972 | textWidthBasis: widget.textWidthBasis, |
1973 | textHeightBehavior: widget.textHeightBehavior, |
1974 | child: widget.child, |
1975 | ); |
1976 | } |
1977 | } |
1978 | |
1979 | /// Animated version of [PhysicalModel]. |
1980 | /// |
1981 | /// The [borderRadius] and [elevation] are animated. |
1982 | /// |
1983 | /// The [color] is animated if the [animateColor] property is set; otherwise, |
1984 | /// the color changes immediately at the start of the animation for the other |
1985 | /// two properties. This allows the color to be animated independently (e.g. |
1986 | /// because it is being driven by an [AnimatedTheme]). |
1987 | /// |
1988 | /// The [shape] is not animated. |
1989 | /// |
1990 | /// Here's an illustration of what using this widget looks like, using a [curve] |
1991 | /// of [Curves.fastOutSlowIn]. |
1992 | /// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_physical_model.mp4} |
1993 | class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget { |
1994 | /// Creates a widget that animates the properties of a [PhysicalModel]. |
1995 | /// |
1996 | /// The [elevation] must be non-negative. |
1997 | /// |
1998 | /// Animating [color] is optional and is controlled by the [animateColor] flag. |
1999 | /// |
2000 | /// Animating [shadowColor] is optional and is controlled by the [animateShadowColor] flag. |
2001 | const AnimatedPhysicalModel({ |
2002 | super.key, |
2003 | required this.child, |
2004 | this.shape = BoxShape.rectangle, |
2005 | this.clipBehavior = Clip.none, |
2006 | this.borderRadius, |
2007 | this.elevation = 0.0, |
2008 | required this.color, |
2009 | this.animateColor = true, |
2010 | required this.shadowColor, |
2011 | this.animateShadowColor = true, |
2012 | super.curve, |
2013 | required super.duration, |
2014 | super.onEnd, |
2015 | }) : assert(elevation >= 0.0); |
2016 | |
2017 | /// The widget below this widget in the tree. |
2018 | /// |
2019 | /// {@macro flutter.widgets.ProxyWidget.child} |
2020 | final Widget child; |
2021 | |
2022 | /// The type of shape. |
2023 | /// |
2024 | /// This property is not animated. |
2025 | final BoxShape shape; |
2026 | |
2027 | /// {@macro flutter.material.Material.clipBehavior} |
2028 | /// |
2029 | /// Defaults to [Clip.none]. |
2030 | final Clip clipBehavior; |
2031 | |
2032 | /// The target border radius of the rounded corners for a rectangle shape. |
2033 | /// |
2034 | /// If null, treated as [BorderRadius.zero]. |
2035 | final BorderRadius? borderRadius; |
2036 | |
2037 | /// The target z-coordinate relative to the parent at which to place this |
2038 | /// physical object. |
2039 | /// |
2040 | /// The value will always be non-negative. |
2041 | final double elevation; |
2042 | |
2043 | /// The target background color. |
2044 | final Color color; |
2045 | |
2046 | /// Whether the color should be animated. |
2047 | final bool animateColor; |
2048 | |
2049 | /// The target shadow color. |
2050 | final Color shadowColor; |
2051 | |
2052 | /// Whether the shadow color should be animated. |
2053 | final bool animateShadowColor; |
2054 | |
2055 | @override |
2056 | AnimatedWidgetBaseState<AnimatedPhysicalModel> createState() => _AnimatedPhysicalModelState(); |
2057 | |
2058 | @override |
2059 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
2060 | super.debugFillProperties(properties); |
2061 | properties.add(EnumProperty<BoxShape>('shape' , shape)); |
2062 | properties.add(DiagnosticsProperty<BorderRadius>('borderRadius' , borderRadius)); |
2063 | properties.add(DoubleProperty('elevation' , elevation)); |
2064 | properties.add(ColorProperty('color' , color)); |
2065 | properties.add(DiagnosticsProperty<bool>('animateColor' , animateColor)); |
2066 | properties.add(ColorProperty('shadowColor' , shadowColor)); |
2067 | properties.add(DiagnosticsProperty<bool>('animateShadowColor' , animateShadowColor)); |
2068 | } |
2069 | } |
2070 | |
2071 | class _AnimatedPhysicalModelState extends AnimatedWidgetBaseState<AnimatedPhysicalModel> { |
2072 | BorderRadiusTween? _borderRadius; |
2073 | Tween<double>? _elevation; |
2074 | ColorTween? _color; |
2075 | ColorTween? _shadowColor; |
2076 | |
2077 | @override |
2078 | void forEachTween(TweenVisitor<dynamic> visitor) { |
2079 | _borderRadius = visitor( |
2080 | _borderRadius, |
2081 | widget.borderRadius ?? BorderRadius.zero, |
2082 | (dynamic value) => BorderRadiusTween(begin: value as BorderRadius), |
2083 | ) as BorderRadiusTween?; |
2084 | _elevation = visitor( |
2085 | _elevation, |
2086 | widget.elevation, |
2087 | (dynamic value) => Tween<double>(begin: value as double), |
2088 | ) as Tween<double>?; |
2089 | _color = visitor( |
2090 | _color, |
2091 | widget.color, |
2092 | (dynamic value) => ColorTween(begin: value as Color), |
2093 | ) as ColorTween?; |
2094 | _shadowColor = visitor( |
2095 | _shadowColor, |
2096 | widget.shadowColor, |
2097 | (dynamic value) => ColorTween(begin: value as Color), |
2098 | ) as ColorTween?; |
2099 | } |
2100 | |
2101 | @override |
2102 | Widget build(BuildContext context) { |
2103 | return PhysicalModel( |
2104 | shape: widget.shape, |
2105 | clipBehavior: widget.clipBehavior, |
2106 | borderRadius: _borderRadius!.evaluate(animation), |
2107 | elevation: _elevation!.evaluate(animation), |
2108 | color: widget.animateColor ? _color!.evaluate(animation)! : widget.color, |
2109 | shadowColor: widget.animateShadowColor |
2110 | ? _shadowColor!.evaluate(animation)! |
2111 | : widget.shadowColor, |
2112 | child: widget.child, |
2113 | ); |
2114 | } |
2115 | } |
2116 | |
2117 | /// Animated version of [FractionallySizedBox] which automatically transitions the |
2118 | /// child's size over a given duration whenever the given [widthFactor] or |
2119 | /// [heightFactor] changes, as well as the position whenever the given [alignment] |
2120 | /// changes. |
2121 | /// |
2122 | /// For the animation, you can choose a [curve] as well as a [duration] and the |
2123 | /// widget will automatically animate to the new target [widthFactor] or |
2124 | /// [heightFactor]. |
2125 | /// |
2126 | /// {@tool dartpad} |
2127 | /// The following example transitions an [AnimatedFractionallySizedBox] |
2128 | /// between two states. It adjusts the [heightFactor], [widthFactor], and |
2129 | /// [alignment] properties when tapped, using a [curve] of [Curves.fastOutSlowIn] |
2130 | /// |
2131 | /// ** See code in examples/api/lib/widgets/implicit_animations/animated_fractionally_sized_box.0.dart ** |
2132 | /// {@end-tool} |
2133 | /// |
2134 | /// See also: |
2135 | /// |
2136 | /// * [AnimatedAlign], which is an implicitly animated version of [Align]. |
2137 | /// * [AnimatedContainer], which can transition more values at once. |
2138 | /// * [AnimatedSlide], which can animate the translation of child by a given offset relative to its size. |
2139 | /// * [AnimatedPositioned], which, as a child of a [Stack], automatically |
2140 | /// transitions its child's position over a given duration whenever the given |
2141 | /// position changes. |
2142 | class AnimatedFractionallySizedBox extends ImplicitlyAnimatedWidget { |
2143 | /// Creates a widget that sizes its child to a fraction of the total available |
2144 | /// space that animates implicitly, and positions its child by an alignment |
2145 | /// that animates implicitly. |
2146 | /// |
2147 | /// If non-null, the [widthFactor] and [heightFactor] arguments must be |
2148 | /// non-negative. |
2149 | const AnimatedFractionallySizedBox({ |
2150 | super.key, |
2151 | this.alignment = Alignment.center, |
2152 | this.child, |
2153 | this.heightFactor, |
2154 | this.widthFactor, |
2155 | super.curve, |
2156 | required super.duration, |
2157 | super.onEnd, |
2158 | }) : assert(widthFactor == null || widthFactor >= 0.0), |
2159 | assert(heightFactor == null || heightFactor >= 0.0); |
2160 | |
2161 | /// The widget below this widget in the tree. |
2162 | /// |
2163 | /// {@macro flutter.widgets.ProxyWidget.child} |
2164 | final Widget? child; |
2165 | |
2166 | /// {@macro flutter.widgets.basic.fractionallySizedBox.heightFactor} |
2167 | final double? heightFactor; |
2168 | |
2169 | /// {@macro flutter.widgets.basic.fractionallySizedBox.widthFactor} |
2170 | final double? widthFactor; |
2171 | |
2172 | /// {@macro flutter.widgets.basic.fractionallySizedBox.alignment} |
2173 | final AlignmentGeometry alignment; |
2174 | |
2175 | @override |
2176 | AnimatedWidgetBaseState<AnimatedFractionallySizedBox> createState() => _AnimatedFractionallySizedBoxState(); |
2177 | |
2178 | @override |
2179 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
2180 | super.debugFillProperties(properties); |
2181 | properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment' , alignment)); |
2182 | properties.add(DiagnosticsProperty<double>('widthFactor' , widthFactor)); |
2183 | properties.add(DiagnosticsProperty<double>('heightFactor' , heightFactor)); |
2184 | } |
2185 | } |
2186 | |
2187 | class _AnimatedFractionallySizedBoxState extends AnimatedWidgetBaseState<AnimatedFractionallySizedBox> { |
2188 | AlignmentGeometryTween? _alignment; |
2189 | Tween<double>? _heightFactorTween; |
2190 | Tween<double>? _widthFactorTween; |
2191 | |
2192 | @override |
2193 | void forEachTween(TweenVisitor<dynamic> visitor) { |
2194 | _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?; |
2195 | if (widget.heightFactor != null) { |
2196 | _heightFactorTween = visitor(_heightFactorTween, widget.heightFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
2197 | } |
2198 | if (widget.widthFactor != null) { |
2199 | _widthFactorTween = visitor(_widthFactorTween, widget.widthFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?; |
2200 | } |
2201 | } |
2202 | |
2203 | @override |
2204 | Widget build(BuildContext context) { |
2205 | return FractionallySizedBox( |
2206 | alignment: _alignment!.evaluate(animation)!, |
2207 | heightFactor: _heightFactorTween?.evaluate(animation), |
2208 | widthFactor: _widthFactorTween?.evaluate(animation), |
2209 | child: widget.child, |
2210 | ); |
2211 | } |
2212 | |
2213 | @override |
2214 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
2215 | super.debugFillProperties(description); |
2216 | description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment' , _alignment, defaultValue: null)); |
2217 | description.add(DiagnosticsProperty<Tween<double>>('widthFactor' , _widthFactorTween, defaultValue: null)); |
2218 | description.add(DiagnosticsProperty<Tween<double>>('heightFactor' , _heightFactorTween, defaultValue: null)); |
2219 | } |
2220 | } |
2221 | |