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