implicit_animations.dart 73.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Adam Barth's avatar
Adam Barth committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui' as ui show TextHeightBehavior;

7
import 'package:flutter/foundation.dart';
8
import 'package:flutter/rendering.dart';
9 10
import 'package:vector_math/vector_math_64.dart';

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';
Adam Barth's avatar
Adam Barth committed
18

19 20
// Examples can assume:
// class MyWidget extends ImplicitlyAnimatedWidget {
21 22
//   const MyWidget({Key? key, this.targetColor = Colors.black}) : super(key: key, duration: const Duration(seconds: 1));
//   final Color targetColor;
23 24 25
//   @override
//   MyWidgetState createState() => MyWidgetState();
// }
26
// void setState(VoidCallback fn) { }
27

28
/// An interpolation between two [BoxConstraints].
29 30 31 32 33
///
/// This class specializes the interpolation of [Tween<BoxConstraints>] to use
/// [BoxConstraints.lerp].
///
/// See [Tween] for a discussion on how to use interpolation objects.
34
class BoxConstraintsTween extends Tween<BoxConstraints> {
35
  /// Creates a [BoxConstraints] tween.
36
  ///
37 38
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as a tight constraint of zero size.
39
  BoxConstraintsTween({ BoxConstraints? begin, BoxConstraints? end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
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)!;
Adam Barth's avatar
Adam Barth committed
44 45
}

46
/// An interpolation between two [Decoration]s.
47 48 49 50
///
/// This class specializes the interpolation of [Tween<BoxConstraints>] to use
/// [Decoration.lerp].
///
51 52 53
/// For [ShapeDecoration]s which know how to [ShapeDecoration.lerpTo] or
/// [ShapeDecoration.lerpFrom] each other, this will produce a smooth
/// interpolation between decorations.
54
///
55
/// See also:
56
///
57 58 59 60 61 62
///  * [Tween] for a discussion on how to use interpolation objects.
///  * [ShapeDecoration], [RoundedRectangleBorder], [CircleBorder], and
///    [StadiumBorder] for examples of shape borders that can be smoothly
///    interpolated.
///  * [BoxBorder] for a border that can only be smoothly interpolated between other
///    [BoxBorder]s.
63
class DecorationTween extends Tween<Decoration> {
64 65
  /// Creates a decoration tween.
  ///
66 67 68 69
  /// The [begin] and [end] properties may be null. If both are null, then the
  /// result is always null. If [end] is not null, then its lerping logic is
  /// used (via [Decoration.lerpTo]). Otherwise, [begin]'s lerping logic is used
  /// (via [Decoration.lerpFrom]).
70
  DecorationTween({ Decoration? begin, Decoration? end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
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)!;
Adam Barth's avatar
Adam Barth committed
75 76
}

77
/// An interpolation between two [EdgeInsets]s.
78 79 80 81 82
///
/// This class specializes the interpolation of [Tween<EdgeInsets>] to use
/// [EdgeInsets.lerp].
///
/// See [Tween] for a discussion on how to use interpolation objects.
83 84 85 86 87
///
/// See also:
///
///  * [EdgeInsetsGeometryTween], which interpolates between two
///    [EdgeInsetsGeometry] objects.
88
class EdgeInsetsTween extends Tween<EdgeInsets> {
89
  /// Creates an [EdgeInsets] tween.
90
  ///
91 92
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as an [EdgeInsets] with no inset.
93
  EdgeInsetsTween({ EdgeInsets? begin, EdgeInsets? end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
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)!;
Adam Barth's avatar
Adam Barth committed
98 99
}

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
/// An interpolation between two [EdgeInsetsGeometry]s.
///
/// This class specializes the interpolation of [Tween<EdgeInsetsGeometry>] to
/// use [EdgeInsetsGeometry.lerp].
///
/// See [Tween] for a discussion on how to use interpolation objects.
///
/// See also:
///
///  * [EdgeInsetsTween], which interpolates between two [EdgeInsets] objects.
class EdgeInsetsGeometryTween extends Tween<EdgeInsetsGeometry> {
  /// Creates an [EdgeInsetsGeometry] tween.
  ///
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as an [EdgeInsetsGeometry] with no inset.
115
  EdgeInsetsGeometryTween({ EdgeInsetsGeometry? begin, EdgeInsetsGeometry? end }) : super(begin: begin, end: end);
116 117 118

  /// Returns the value this variable has at the given animation clock value.
  @override
119
  EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t)!;
120 121
}

122
/// An interpolation between two [BorderRadius]s.
123 124 125 126 127
///
/// This class specializes the interpolation of [Tween<BorderRadius>] to use
/// [BorderRadius.lerp].
///
/// See [Tween] for a discussion on how to use interpolation objects.
128
class BorderRadiusTween extends Tween<BorderRadius?> {
129
  /// Creates a [BorderRadius] tween.
130
  ///
131 132
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as a right angle (no radius).
133
  BorderRadiusTween({ BorderRadius? begin, BorderRadius? end }) : super(begin: begin, end: 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 141 142 143 144 145
/// An interpolation between two [Border]s.
///
/// This class specializes the interpolation of [Tween<Border>] to use
/// [Border.lerp].
///
/// See [Tween] for a discussion on how to use interpolation objects.
146
class BorderTween extends Tween<Border?> {
147 148 149 150
  /// Creates a [Border] tween.
  ///
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as having no border.
151
  BorderTween({ Border? begin, Border? end }) : super(begin: begin, end: end);
152 153 154

  /// Returns the value this variable has at the given animation clock value.
  @override
155
  Border? lerp(double t) => Border.lerp(begin, end, t);
156 157
}

158
/// An interpolation between two [Matrix4]s.
159
///
160 161 162
/// This class specializes the interpolation of [Tween<Matrix4>] to be
/// appropriate for transformation matrices.
///
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 168
  /// Creates a [Matrix4] tween.
  ///
169 170 171
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
172
  Matrix4Tween({ Matrix4? begin, Matrix4? end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
173

174
  @override
Adam Barth's avatar
Adam Barth committed
175
  Matrix4 lerp(double t) {
176 177
    assert(begin != null);
    assert(end != null);
178 179 180 181 182 183
    final Vector3 beginTranslation = Vector3.zero();
    final Vector3 endTranslation = Vector3.zero();
    final Quaternion beginRotation = Quaternion.identity();
    final Quaternion endRotation = Quaternion.identity();
    final Vector3 beginScale = Vector3.zero();
    final Vector3 endScale = Vector3.zero();
184 185
    begin!.decompose(beginTranslation, beginRotation, beginScale);
    end!.decompose(endTranslation, endRotation, endScale);
186 187 188 189 190 191
    final Vector3 lerpTranslation =
        beginTranslation * (1.0 - t) + endTranslation * t;
    // TODO(alangardner): Implement slerp for constant rotation
    final Quaternion lerpRotation =
        (beginRotation.scaled(1.0 - t) + endRotation.scaled(t)).normalized();
    final Vector3 lerpScale = beginScale * (1.0 - t) + endScale * t;
192
    return Matrix4.compose(lerpTranslation, lerpRotation, lerpScale);
Adam Barth's avatar
Adam Barth committed
193 194 195
  }
}

196 197
/// An interpolation between two [TextStyle]s.
///
198 199 200
/// This class specializes the interpolation of [Tween<TextStyle>] to use
/// [TextStyle.lerp].
///
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 206
  /// Creates a text style tween.
  ///
207 208 209
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
210
  TextStyleTween({ TextStyle? begin, TextStyle? end }) : super(begin: begin, end: 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 218
/// An abstract class for building widgets that animate changes to their
/// properties.
219
///
220 221 222 223 224
/// Widgets of this type will not animate when they are first added to the
/// widget tree. Rather, when they are rebuilt with different values, they will
/// respond to those _changes_ by animating the changes over a specified
/// [duration].
///
225
/// Which properties are animated is left up to the subclass. Subclasses' [State]s
226 227
/// must extend [ImplicitlyAnimatedWidgetState] and provide a way to visit the
/// relevant fields to animate.
228 229 230 231 232 233 234 235 236
///
/// ## Relationship to [AnimatedWidget]s
///
/// [ImplicitlyAnimatedWidget]s (and their subclasses) automatically animate
/// changes in their properties whenever they change. For this,
/// they create and manage their own internal [AnimationController]s to power
/// the animation. While these widgets are simple to use and don't require you
/// to manually manage the lifecycle of an [AnimationController], they
/// 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 240
/// to stop it somewhere in the middle), consider using an
/// [AnimatedWidget] or one of its subclasses. These widgets take an [Animation]
241 242 243 244 245 246 247 248 249 250
/// as an argument to power the animation. This gives the developer full control
/// over the animation at the cost of requiring you to manually manage the
/// underlying [AnimationController].
///
/// ## Common implicitly animated widgets
///
/// A number of implicitly animated widgets ship with the framework. They are
/// usually named `AnimatedFoo`, where `Foo` is the name of the non-animated
/// version of that widget. Commonly used implicitly animated widgets include:
///
251 252
///  * [TweenAnimationBuilder], which animates any property expressed by
///    a [Tween] to a specified target value.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
///  * [AnimatedAlign], which is an implicitly animated version of [Align].
///  * [AnimatedContainer], which is an implicitly animated version of
///    [Container].
///  * [AnimatedDefaultTextStyle], which is an implicitly animated version of
///    [DefaultTextStyle].
///  * [AnimatedOpacity], which is an implicitly animated version of [Opacity].
///  * [AnimatedPadding], which is an implicitly animated version of [Padding].
///  * [AnimatedPhysicalModel], which is an implicitly animated version of
///    [PhysicalModel].
///  * [AnimatedPositioned], which is an implicitly animated version of
///    [Positioned].
///  * [AnimatedPositionedDirectional], which is an implicitly animated version
///    of [PositionedDirectional].
///  * [AnimatedTheme], which is an implicitly animated version of [Theme].
///  * [AnimatedCrossFade], which cross-fades between two given children and
///    animates itself between their sizes.
///  * [AnimatedSize], which automatically transitions its size over a given
///    duration.
///  * [AnimatedSwitcher], which fades from one widget to another.
272
abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
273 274 275
  /// Initializes fields for subclasses.
  ///
  /// The [curve] and [duration] arguments must not be null.
276
  const ImplicitlyAnimatedWidget({
277
    Key? key,
278
    this.curve = Curves.linear,
279
    required this.duration,
280
    this.onEnd,
281 282 283
  }) : assert(curve != null),
       assert(duration != null),
       super(key: key);
Adam Barth's avatar
Adam Barth committed
284

285
  /// The curve to apply when animating the parameters of this container.
Adam Barth's avatar
Adam Barth committed
286
  final Curve curve;
287 288

  /// The duration over which to animate the parameters of this container.
Adam Barth's avatar
Adam Barth committed
289 290
  final Duration duration;

291 292 293 294
  /// Called every time an animation completes.
  ///
  /// This can be useful to trigger additional actions (e.g. another animation)
  /// at the end of the current animation.
295
  final VoidCallback? onEnd;
296

297
  @override
Ian Hickson's avatar
Ian Hickson committed
298
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState(); // ignore: no_logic_in_create_state, https://github.com/dart-lang/linter/issues/2345
299

300
  @override
301 302
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
303
    properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
304
  }
Adam Barth's avatar
Adam Barth committed
305 306
}

307 308 309 310
/// Signature for a [Tween] factory.
///
/// This is the type of one of the arguments of [TweenVisitor], the signature
/// used by [AnimatedWidgetBaseState.forEachTween].
311 312 313
///
/// Instances of this function are expected to take a value and return a tween
/// beginning at that value.
314
typedef TweenConstructor<T extends Object> = Tween<T> Function(T targetValue);
315

316 317
/// Signature for callbacks passed to [ImplicitlyAnimatedWidgetState.forEachTween].
///
318
/// {@template flutter.widgets.TweenVisitor.arguments}
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
/// The `tween` argument should contain the current tween value. This will
/// initially be null when the state is first initialized.
///
/// The `targetValue` argument should contain the value toward which the state
/// is animating. For instance, if the state is animating its widget's
/// opacity value, then this argument should contain the widget's current
/// opacity value.
///
/// The `constructor` argument should contain a function that takes a value
/// (the widget's value being animated) and returns a tween beginning at that
/// value.
///
/// {@endtemplate}
///
/// `forEachTween()` is expected to update its tween value to the return value
/// of this visitor.
///
/// The `<T>` parameter specifies the type of value that's being animated.
337
typedef TweenVisitor<T extends Object> = Tween<T>? Function(Tween<T>? tween, T targetValue, TweenConstructor<T> constructor);
Adam Barth's avatar
Adam Barth committed
338

339
/// A base class for the `State` of widgets with implicit animations.
340
///
341
/// [ImplicitlyAnimatedWidgetState] requires that subclasses respond to the
342
/// animation themselves. If you would like `setState()` to be called
343 344
/// automatically as the animation changes, use [AnimatedWidgetBaseState].
///
345 346 347 348
/// Properties that subclasses choose to animate are represented by [Tween]
/// instances. Subclasses must implement the [forEachTween] method to allow
/// [ImplicitlyAnimatedWidgetState] to iterate through the widget's fields and
/// animate them.
349
abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
350 351
  /// The animation controller driving this widget's implicit animations.
  @protected
352 353 354 355 356 357
  AnimationController get controller => _controller;
  late final AnimationController _controller = AnimationController(
    duration: widget.duration,
    debugLabel: kDebugMode ? widget.toStringShort() : null,
    vsync: this,
  );
358

359
  /// The animation driving this widget's implicit animations.
360 361
  Animation<double> get animation => _animation;
  late Animation<double> _animation = _createCurve();
Adam Barth's avatar
Adam Barth committed
362

363
  @override
Adam Barth's avatar
Adam Barth committed
364 365
  void initState() {
    super.initState();
366
    _controller.addStatusListener((AnimationStatus status) {
367 368
      switch (status) {
        case AnimationStatus.completed:
369
          widget.onEnd?.call();
370 371 372 373 374 375
          break;
        case AnimationStatus.dismissed:
        case AnimationStatus.forward:
        case AnimationStatus.reverse:
      }
    });
376
    _constructTweens();
377
    didUpdateTweens();
Adam Barth's avatar
Adam Barth committed
378 379
  }

380
  @override
381
  void didUpdateWidget(T oldWidget) {
382
    super.didUpdateWidget(oldWidget);
383
    if (widget.curve != oldWidget.curve)
384 385
      _animation = _createCurve();
    _controller.duration = widget.duration;
386
    if (_constructTweens()) {
387
      forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
388 389
        _updateTween(tween, targetValue);
        return tween;
390
      });
391
      _controller
392 393
        ..value = 0.0
        ..forward();
394
      didUpdateTweens();
Adam Barth's avatar
Adam Barth committed
395 396 397
    }
  }

398 399
  CurvedAnimation _createCurve() {
    return CurvedAnimation(parent: _controller, curve: widget.curve);
400 401
  }

402
  @override
Adam Barth's avatar
Adam Barth committed
403
  void dispose() {
404
    _controller.dispose();
Adam Barth's avatar
Adam Barth committed
405 406 407
    super.dispose();
  }

408
  bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) {
409
    return targetValue != (tween.end ?? tween.begin);
410 411
  }

412
  void _updateTween(Tween<dynamic>? tween, dynamic targetValue) {
413 414 415
    if (tween == null)
      return;
    tween
416
      ..begin = tween.evaluate(_animation)
417
      ..end = targetValue;
Adam Barth's avatar
Adam Barth committed
418 419
  }

420 421
  bool _constructTweens() {
    bool shouldStartAnimation = false;
422
    forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
423
      if (targetValue != null) {
424 425 426
        tween ??= constructor(targetValue);
        if (_shouldAnimateTween(tween, targetValue))
          shouldStartAnimation = true;
427
      } else {
428
        tween = null;
429
      }
430
      return tween;
431
    });
432
    return shouldStartAnimation;
433
  }
Adam Barth's avatar
Adam Barth committed
434

435 436
  /// Visits each tween controlled by this state with the specified `visitor`
  /// function.
437
  ///
438
  /// ### Subclass responsibility
439
  ///
440 441 442 443 444 445
  /// Properties to be animated are represented by [Tween] member variables in
  /// the state. For each such tween, [forEachTween] implementations are
  /// expected to call `visitor` with the appropriate arguments and store the
  /// result back into the member variable. The arguments to `visitor` are as
  /// follows:
  ///
446
  /// {@macro flutter.widgets.TweenVisitor.arguments}
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
  ///
  /// ### When this method will be called
  ///
  /// [forEachTween] is initially called during [initState]. It is expected that
  /// the visitor's `tween` argument will be set to null, causing the visitor to
  /// call its `constructor` argument to construct the tween for the first time.
  /// The resulting tween will have its `begin` value set to the target value
  /// and will have its `end` value set to null. The animation will not be
  /// started.
  ///
  /// When this state's [widget] is updated (thus triggering the
  /// [didUpdateWidget] method to be called), [forEachTween] will be called
  /// again to check if the target value has changed. If the target value has
  /// changed, signaling that the [animation] should start, then the visitor
  /// will update the tween's `start` and `end` values accordingly, and the
  /// animation will be started.
  ///
  /// ### Other member variables
465 466 467 468 469
  ///
  /// Subclasses that contain properties based on tweens created by
  /// [forEachTween] should override [didUpdateTweens] to update those
  /// properties. Dependent properties should not be updated within
  /// [forEachTween].
470
  ///
471
  /// {@tool snippet}
472
  ///
473
  /// This sample implements an implicitly animated widget's `State`.
474 475 476 477 478
  /// The widget animates between colors whenever `widget.targetColor`
  /// changes.
  ///
  /// ```dart
  /// class MyWidgetState extends AnimatedWidgetBaseState<MyWidget> {
479
  ///   ColorTween? _colorTween;
480 481 482 483 484 485
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     return Text(
  ///       'Hello World',
  ///       // Computes the value of the text color at any given time.
486
  ///       style: TextStyle(color: _colorTween?.evaluate(animation)),
487 488 489 490 491 492 493 494 495 496 497 498 499
  ///     );
  ///   }
  ///
  ///   @override
  ///   void forEachTween(TweenVisitor<dynamic> visitor) {
  ///     // Update the tween using the provided visitor function.
  ///     _colorTween = visitor(
  ///       // The latest tween value. Can be `null`.
  ///       _colorTween,
  ///       // The color value toward which we are animating.
  ///       widget.targetColor,
  ///       // A function that takes a color value and returns a tween
  ///       // beginning at that value.
500
  ///       (dynamic value) => ColorTween(begin: value as Color?),
501
  ///     ) as ColorTween?;
502 503 504 505 506 507 508
  ///
  ///     // We could have more tweens than one by using the visitor
  ///     // multiple times.
  ///   }
  /// }
  /// ```
  /// {@end-tool}
509
  @protected
510
  void forEachTween(TweenVisitor<dynamic> visitor);
511 512 513 514 515 516

  /// Optional hook for subclasses that runs after all tweens have been updated
  /// via [forEachTween].
  ///
  /// Any properties that depend upon tweens created by [forEachTween] should be
  /// updated within [didUpdateTweens], not within [forEachTween].
517 518 519 520 521 522 523 524 525 526 527 528 529 530
  ///
  /// This method will be called both:
  ///
  ///  1. After the tweens are _initially_ constructed (by
  ///     the `constructor` argument to the [TweenVisitor] that's passed to
  ///     [forEachTween]). In this case, the tweens are likely to contain only
  ///     a [Tween.begin] value and not a [Tween.end].
  ///
  ///  2. When the state's [widget] is updated, and one or more of the tweens
  ///     visited by [forEachTween] specifies a target value that's different
  ///     than the widget's current value, thus signaling that the [animation]
  ///     should run. In this case, the [Tween.begin] value for each tween will
  ///     an evaluation of the tween against the current [animation], and the
  ///     [Tween.end] value for each tween will be the target value.
531
  @protected
532
  void didUpdateTweens() { }
533
}
Adam Barth's avatar
Adam Barth committed
534

535 536 537 538 539 540 541 542 543 544 545 546 547 548
/// A base class for widgets with implicit animations that need to rebuild their
/// widget tree as the animation runs.
///
/// This class calls [build] each frame that the animation tickets. For a
/// variant that does not rebuild each frame, consider subclassing
/// [ImplicitlyAnimatedWidgetState] directly.
///
/// Subclasses must implement the [forEachTween] method to allow
/// [AnimatedWidgetBaseState] to iterate through the subclasses' widget's fields
/// and animate them.
abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> extends ImplicitlyAnimatedWidgetState<T> {
  @override
  void initState() {
    super.initState();
549
    controller.addListener(_handleAnimationChanged);
550 551 552 553 554 555 556
  }

  void _handleAnimationChanged() {
    setState(() { /* The animation ticked. Rebuild with new animation value */ });
  }
}

557
/// Animated version of [Container] that gradually changes its values over a period of time.
558
///
559 560
/// The [AnimatedContainer] will automatically animate between the old and
/// new values of properties when they change using the provided curve and
561 562
/// duration. Properties that are null are not animated. Its child and
/// descendants are not animated.
563
///
564
/// This class is useful for generating simple implicit transitions between
565 566 567 568
/// different parameters to [Container] with its internal [AnimationController].
/// For more complex animations, you'll likely want to use a subclass of
/// [AnimatedWidget] such as the [DecoratedBoxTransition] or use your own
/// [AnimationController].
Ian Hickson's avatar
Ian Hickson committed
569
///
570 571
/// {@youtube 560 315 https://www.youtube.com/watch?v=yI-8QHpGIP4}
///
572
/// {@tool dartpad --template=stateful_widget_scaffold}
573 574
///
/// The following example (depicted above) transitions an AnimatedContainer
575
/// between two states. It adjusts the `height`, `width`, `color`, and
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
/// [alignment] properties when tapped.
///
/// ```dart
/// bool selected = false;
///
/// @override
/// Widget build(BuildContext context) {
///   return GestureDetector(
///     onTap: () {
///       setState(() {
///         selected = !selected;
///       });
///     },
///     child: Center(
///       child: AnimatedContainer(
///         width: selected ? 200.0 : 100.0,
///         height: selected ? 100.0 : 200.0,
///         color: selected ? Colors.red : Colors.blue,
///         alignment: selected ? Alignment.center : AlignmentDirectional.topCenter,
595
///         duration: const Duration(seconds: 2),
596
///         curve: Curves.fastOutSlowIn,
597
///         child: const FlutterLogo(size: 75),
598 599 600 601 602 603 604
///       ),
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
Ian Hickson's avatar
Ian Hickson committed
605 606 607 608
/// See also:
///
///  * [AnimatedPadding], which is a subset of this widget that only
///    supports animating the [padding].
609
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
610 611 612 613 614
///  * [AnimatedPositioned], which, as a child of a [Stack], automatically
///    transitions its child's position over a given duration whenever the given
///    position changes.
///  * [AnimatedAlign], which automatically transitions its child's
///    position over a given duration whenever the given [alignment] changes.
615 616
///  * [AnimatedSwitcher], which switches out a child for a new one with a customizable transition.
///  * [AnimatedCrossFade], which fades between two children and interpolates their sizes.
617
class AnimatedContainer extends ImplicitlyAnimatedWidget {
618 619 620
  /// Creates a container that animates its parameters implicitly.
  ///
  /// The [curve] and [duration] arguments must not be null.
621
  AnimatedContainer({
622
    Key? key,
623 624
    this.alignment,
    this.padding,
625 626
    Color? color,
    Decoration? decoration,
627
    this.foregroundDecoration,
628 629 630
    double? width,
    double? height,
    BoxConstraints? constraints,
631 632
    this.margin,
    this.transform,
633
    this.transformAlignment,
634
    this.child,
635
    this.clipBehavior = Clip.none,
636
    Curve curve = Curves.linear,
637 638
    required Duration duration,
    VoidCallback? onEnd,
639 640 641 642 643 644
  }) : assert(margin == null || margin.isNonNegative),
       assert(padding == null || padding.isNonNegative),
       assert(decoration == null || decoration.debugAssertIsValid()),
       assert(constraints == null || constraints.debugAssertIsValid()),
       assert(color == null || decoration == null,
         'Cannot provide both a color and a decoration\n'
645
         'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".',
646
       ),
647
       decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
648 649 650
       constraints =
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
651
            ?? BoxConstraints.tightFor(width: width, height: height)
652
          : constraints,
653
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
Adam Barth's avatar
Adam Barth committed
654

655 656 657 658 659 660
  /// The [child] contained by the container.
  ///
  /// If null, and if the [constraints] are unbounded or also null, the
  /// container will expand to fill all available space in its parent, unless
  /// the parent provides unbounded constraints, in which case the container
  /// will attempt to be as small as possible.
661
  ///
662
  /// {@macro flutter.widgets.ProxyWidget.child}
663
  final Widget? child;
Adam Barth's avatar
Adam Barth committed
664

665 666 667 668 669 670 671
  /// Align the [child] within the container.
  ///
  /// If non-null, the container will expand to fill its parent and position its
  /// child within itself according to the given value. If the incoming
  /// constraints are unbounded, then the child will be shrink-wrapped instead.
  ///
  /// Ignored if [child] is null.
672 673 674 675 676 677 678
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
679
  final AlignmentGeometry? alignment;
680 681 682

  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
  /// placed inside this padding.
683
  final EdgeInsetsGeometry? padding;
Adam Barth's avatar
Adam Barth committed
684

685 686 687 688 689
  /// The decoration to paint behind the [child].
  ///
  /// A shorthand for specifying just a solid color is available in the
  /// constructor: set the `color` argument instead of the `decoration`
  /// argument.
690
  final Decoration? decoration;
Adam Barth's avatar
Adam Barth committed
691

692
  /// The decoration to paint in front of the child.
693
  final Decoration? foregroundDecoration;
Adam Barth's avatar
Adam Barth committed
694

695 696 697 698 699 700
  /// Additional constraints to apply to the child.
  ///
  /// The constructor `width` and `height` arguments are combined with the
  /// `constraints` argument to set this property.
  ///
  /// The [padding] goes inside the constraints.
701
  final BoxConstraints? constraints;
Adam Barth's avatar
Adam Barth committed
702

703
  /// Empty space to surround the [decoration] and [child].
704
  final EdgeInsetsGeometry? margin;
705 706

  /// The transformation matrix to apply before painting the container.
707
  final Matrix4? transform;
708

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
  /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
  ///
  /// When [transform] is null, the value of this property is ignored.
  ///
  /// See also:
  ///
  ///  * [Transform.alignment], which is set by this property.
  final AlignmentGeometry? transformAlignment;

  /// The clip behavior when [AnimatedContainer.decoration] is not null.
  ///
  /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
  ///
  /// Unlike other properties of [AnimatedContainer], changes to this property
  /// apply immediately and have no animation.
  ///
  /// If a clip is to be applied, the [Decoration.getClipPath] method
  /// for the provided decoration must return a clip path. (This is not
  /// supported by all decorations; the default implementation of that
  /// method throws an [UnsupportedError].)
  final Clip clipBehavior;

731
  @override
732
  AnimatedWidgetBaseState<AnimatedContainer> createState() => _AnimatedContainerState();
733

734
  @override
735 736
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
737 738 739 740 741 742 743
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
    properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
    properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null, showName: false));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
    properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
744 745
    properties.add(DiagnosticsProperty<AlignmentGeometry>('transformAlignment', transformAlignment, defaultValue: null));
    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior));
746
  }
747 748 749
}

class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
750 751 752 753 754 755 756
  AlignmentGeometryTween? _alignment;
  EdgeInsetsGeometryTween? _padding;
  DecorationTween? _decoration;
  DecorationTween? _foregroundDecoration;
  BoxConstraintsTween? _constraints;
  EdgeInsetsGeometryTween? _margin;
  Matrix4Tween? _transform;
757
  AlignmentGeometryTween? _transformAlignment;
758

759
  @override
760
  void forEachTween(TweenVisitor<dynamic> visitor) {
761 762 763 764 765 766 767
    _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
    _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
    _decoration = visitor(_decoration, widget.decoration, (dynamic value) => DecorationTween(begin: value as Decoration)) as DecorationTween?;
    _foregroundDecoration = visitor(_foregroundDecoration, widget.foregroundDecoration, (dynamic value) => DecorationTween(begin: value as Decoration)) as DecorationTween?;
    _constraints = visitor(_constraints, widget.constraints, (dynamic value) => BoxConstraintsTween(begin: value as BoxConstraints)) as BoxConstraintsTween?;
    _margin = visitor(_margin, widget.margin, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
    _transform = visitor(_transform, widget.transform, (dynamic value) => Matrix4Tween(begin: value as Matrix4)) as Matrix4Tween?;
768
    _transformAlignment = visitor(_transformAlignment, widget.transformAlignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
Adam Barth's avatar
Adam Barth committed
769 770
  }

771
  @override
Adam Barth's avatar
Adam Barth committed
772
  Widget build(BuildContext context) {
773
    final Animation<double> animation = this.animation;
774
    return Container(
775 776 777 778 779 780 781 782 783
      alignment: _alignment?.evaluate(animation),
      padding: _padding?.evaluate(animation),
      decoration: _decoration?.evaluate(animation),
      foregroundDecoration: _foregroundDecoration?.evaluate(animation),
      constraints: _constraints?.evaluate(animation),
      margin: _margin?.evaluate(animation),
      transform: _transform?.evaluate(animation),
      transformAlignment: _transformAlignment?.evaluate(animation),
      clipBehavior: widget.clipBehavior,
784
      child: widget.child,
Adam Barth's avatar
Adam Barth committed
785 786
    );
  }
Hixie's avatar
Hixie committed
787

788
  @override
789
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
790
    super.debugFillProperties(description);
791 792 793 794 795 796 797
    description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment', _alignment, showName: false, defaultValue: null));
    description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('padding', _padding, defaultValue: null));
    description.add(DiagnosticsProperty<DecorationTween>('bg', _decoration, defaultValue: null));
    description.add(DiagnosticsProperty<DecorationTween>('fg', _foregroundDecoration, defaultValue: null));
    description.add(DiagnosticsProperty<BoxConstraintsTween>('constraints', _constraints, showName: false, defaultValue: null));
    description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('margin', _margin, defaultValue: null));
    description.add(ObjectFlagProperty<Matrix4Tween>.has('transform', _transform));
798
    description.add(DiagnosticsProperty<AlignmentGeometryTween>('transformAlignment', _transformAlignment, defaultValue: null));
Hixie's avatar
Hixie committed
799
  }
Adam Barth's avatar
Adam Barth committed
800
}
Ian Hickson's avatar
Ian Hickson committed
801

Ian Hickson's avatar
Ian Hickson committed
802 803 804
/// Animated version of [Padding] which automatically transitions the
/// indentation over a given duration whenever the given inset changes.
///
805 806
/// {@youtube 560 315 https://www.youtube.com/watch?v=PY2m0fhGNz4}
///
807 808 809 810
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.fastOutSlowIn].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_padding.mp4}
///
811
/// {@tool dartpad --template=stateful_widget_scaffold}
812 813 814 815 816 817
///
/// The following code implements the [AnimatedPadding] widget, using a [curve] of
/// [Curves.easeInOut].
///
/// ```dart
/// double padValue = 0.0;
818
/// void _updatePadding(double value) {
819 820 821 822 823 824 825 826 827
///   setState(() {
///     padValue = value;
///   });
/// }
///
/// @override
/// Widget build(BuildContext context) {
///   return Column(
///     mainAxisAlignment: MainAxisAlignment.center,
828
///     children: <Widget>[
829 830 831 832 833 834 835 836 837 838 839 840
///       AnimatedPadding(
///         padding: EdgeInsets.all(padValue),
///         duration: const Duration(seconds: 2),
///         curve: Curves.easeInOut,
///         child: Container(
///           width: MediaQuery.of(context).size.width,
///           height: MediaQuery.of(context).size.height / 5,
///           color: Colors.blue,
///         ),
///       ),
///       Text('Padding: $padValue'),
///       ElevatedButton(
841
///         child: const Text('Change padding'),
842 843 844 845 846 847 848 849 850 851
///         onPressed: () {
///           _updatePadding(padValue == 0.0 ? 100.0 : 0.0);
///         }
///       ),
///     ],
///   );
/// }
/// ```
/// {@end-tool}
///
Ian Hickson's avatar
Ian Hickson committed
852 853 854
/// See also:
///
///  * [AnimatedContainer], which can transition more values at once.
855
///  * [AnimatedAlign], which automatically transitions its child's
856 857
///    position over a given duration whenever the given
///    [AnimatedAlign.alignment] changes.
Ian Hickson's avatar
Ian Hickson committed
858 859 860 861 862 863
class AnimatedPadding extends ImplicitlyAnimatedWidget {
  /// Creates a widget that insets its child by a value that animates
  /// implicitly.
  ///
  /// The [padding], [curve], and [duration] arguments must not be null.
  AnimatedPadding({
864 865
    Key? key,
    required this.padding,
Ian Hickson's avatar
Ian Hickson committed
866
    this.child,
867
    Curve curve = Curves.linear,
868 869
    required Duration duration,
    VoidCallback? onEnd,
Ian Hickson's avatar
Ian Hickson committed
870 871
  }) : assert(padding != null),
       assert(padding.isNonNegative),
872
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
Ian Hickson's avatar
Ian Hickson committed
873 874 875 876 877

  /// The amount of space by which to inset the child.
  final EdgeInsetsGeometry padding;

  /// The widget below this widget in the tree.
878
  ///
879
  /// {@macro flutter.widgets.ProxyWidget.child}
880
  final Widget? child;
Ian Hickson's avatar
Ian Hickson committed
881 882

  @override
883
  AnimatedWidgetBaseState<AnimatedPadding> createState() => _AnimatedPaddingState();
Ian Hickson's avatar
Ian Hickson committed
884 885

  @override
886 887
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
888
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
Ian Hickson's avatar
Ian Hickson committed
889 890 891 892
  }
}

class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> {
893
  EdgeInsetsGeometryTween? _padding;
Ian Hickson's avatar
Ian Hickson committed
894 895 896

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
897
    _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
Ian Hickson's avatar
Ian Hickson committed
898 899 900 901
  }

  @override
  Widget build(BuildContext context) {
902
    return Padding(
903
      padding: _padding!
904
        .evaluate(animation)
905
        .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity),
Ian Hickson's avatar
Ian Hickson committed
906 907 908 909 910 911 912
      child: widget.child,
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
913
    description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('padding', _padding, defaultValue: null));
Ian Hickson's avatar
Ian Hickson committed
914 915 916
  }
}

Ian Hickson's avatar
Ian Hickson committed
917 918 919
/// Animated version of [Align] which automatically transitions the child's
/// position over a given duration whenever the given [alignment] changes.
///
920 921 922 923
/// Here's an illustration of what this can look like, using a [curve] of
/// [Curves.fastOutSlowIn].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_align.mp4}
///
924
/// For the animation, you can choose a [curve] as well as a [duration] and the
925 926 927 928 929 930 931
/// widget will automatically animate to the new target [alignment]. If you require
/// more control over the animation (e.g. if you want to stop it mid-animation),
/// consider using an [AlignTransition] instead, which takes a provided
/// [Animation] as argument. While that allows you to fine-tune the animation,
/// it also requires more development overhead as you have to manually manage
/// the lifecycle of the underlying [AnimationController].
///
932
/// {@tool dartpad --template=stateful_widget_scaffold}
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965
///
/// The following code implements the [AnimatedAlign] widget, using a [curve] of
/// [Curves.fastOutSlowIn].
///
/// ```dart
/// bool selected = false;
///
/// @override
/// Widget build(BuildContext context) {
///   return GestureDetector(
///     onTap: () {
///       setState(() {
///         selected = !selected;
///       });
///     },
///     child: Center(
///       child: Container(
///         width: 250.0,
///         height: 250.0,
///         color: Colors.red,
///         child: AnimatedAlign(
///           alignment: selected ? Alignment.topRight : Alignment.bottomLeft,
///           duration: const Duration(seconds: 1),
///           curve: Curves.fastOutSlowIn,
///           child: const FlutterLogo(size: 50.0),
///         ),
///       ),
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
Ian Hickson's avatar
Ian Hickson committed
966 967 968
/// See also:
///
///  * [AnimatedContainer], which can transition more values at once.
969 970 971 972 973
///  * [AnimatedPadding], which can animate the padding instead of the
///    alignment.
///  * [AnimatedPositioned], which, as a child of a [Stack], automatically
///    transitions its child's position over a given duration whenever the given
///    position changes.
Ian Hickson's avatar
Ian Hickson committed
974 975 976 977 978 979
class AnimatedAlign extends ImplicitlyAnimatedWidget {
  /// Creates a widget that positions its child by an alignment that animates
  /// implicitly.
  ///
  /// The [alignment], [curve], and [duration] arguments must not be null.
  const AnimatedAlign({
980 981
    Key? key,
    required this.alignment,
Ian Hickson's avatar
Ian Hickson committed
982
    this.child,
983 984
    this.heightFactor,
    this.widthFactor,
985
    Curve curve = Curves.linear,
986 987
    required Duration duration,
    VoidCallback? onEnd,
Ian Hickson's avatar
Ian Hickson committed
988
  }) : assert(alignment != null),
989 990
       assert(widthFactor == null || widthFactor >= 0.0),
       assert(heightFactor == null || heightFactor >= 0.0),
991
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
Ian Hickson's avatar
Ian Hickson committed
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011

  /// How to align the child.
  ///
  /// The x and y values of the [Alignment] control the horizontal and vertical
  /// alignment, respectively. An x value of -1.0 means that the left edge of
  /// the child is aligned with the left edge of the parent whereas an x value
  /// of 1.0 means that the right edge of the child is aligned with the right
  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
  /// For example, a value of 0.0 means that the center of the child is aligned
  /// with the center of the parent.
  ///
  /// See also:
  ///
  ///  * [Alignment], which has more details and some convenience constants for
  ///    common positions.
  ///  * [AlignmentDirectional], which has a horizontal coordinate orientation
  ///    that depends on the [TextDirection].
  final AlignmentGeometry alignment;

  /// The widget below this widget in the tree.
1012
  ///
1013
  /// {@macro flutter.widgets.ProxyWidget.child}
1014
  final Widget? child;
Ian Hickson's avatar
Ian Hickson committed
1015

1016 1017 1018
  /// If non-null, sets its height to the child's height multiplied by this factor.
  ///
  /// Must be greater than or equal to 0.0, defaults to null.
1019
  final double? heightFactor;
1020 1021 1022 1023

  /// If non-null, sets its width to the child's width multiplied by this factor.
  ///
  /// Must be greater than or equal to 0.0, defaults to null.
1024
  final double? widthFactor;
1025

Ian Hickson's avatar
Ian Hickson committed
1026
  @override
1027
  AnimatedWidgetBaseState<AnimatedAlign> createState() => _AnimatedAlignState();
Ian Hickson's avatar
Ian Hickson committed
1028 1029

  @override
1030 1031
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1032
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
Ian Hickson's avatar
Ian Hickson committed
1033 1034 1035 1036
  }
}

class _AnimatedAlignState extends AnimatedWidgetBaseState<AnimatedAlign> {
1037 1038 1039
  AlignmentGeometryTween? _alignment;
  Tween<double>? _heightFactorTween;
  Tween<double>? _widthFactorTween;
Ian Hickson's avatar
Ian Hickson committed
1040 1041 1042

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
1043
    _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
1044
    if(widget.heightFactor != null) {
1045
      _heightFactorTween = visitor(_heightFactorTween, widget.heightFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
1046 1047
    }
    if(widget.widthFactor != null) {
1048
      _widthFactorTween = visitor(_widthFactorTween, widget.widthFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
1049
    }
Ian Hickson's avatar
Ian Hickson committed
1050 1051 1052 1053
  }

  @override
  Widget build(BuildContext context) {
1054
    return Align(
1055 1056 1057
      alignment: _alignment!.evaluate(animation)!,
      heightFactor: _heightFactorTween?.evaluate(animation),
      widthFactor: _widthFactorTween?.evaluate(animation),
Ian Hickson's avatar
Ian Hickson committed
1058 1059 1060 1061 1062 1063 1064
      child: widget.child,
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
1065
    description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment', _alignment, defaultValue: null));
1066 1067
    description.add(DiagnosticsProperty<Tween<double>>('widthFactor', _widthFactorTween, defaultValue: null));
    description.add(DiagnosticsProperty<Tween<double>>('heightFactor', _heightFactorTween, defaultValue: null));
Ian Hickson's avatar
Ian Hickson committed
1068 1069 1070
  }
}

Ian Hickson's avatar
Ian Hickson committed
1071
/// Animated version of [Positioned] which automatically transitions the child's
1072
/// position over a given duration whenever the given position changes.
Ian Hickson's avatar
Ian Hickson committed
1073
///
1074 1075
/// {@youtube 560 315 https://www.youtube.com/watch?v=hC3s2YdtWt8}
///
Ian Hickson's avatar
Ian Hickson committed
1076
/// Only works if it's the child of a [Stack].
1077
///
1078 1079 1080 1081 1082 1083 1084
/// This widget is a good choice if the _size_ of the child would end up
/// changing as a result of this animation. If the size is intended to remain
/// the same, with only the _position_ changing over time, then consider
/// [SlideTransition] instead. [SlideTransition] only triggers a repaint each
/// frame of the animation, whereas [AnimatedPositioned] will trigger a relayout
/// as well.
///
1085 1086 1087 1088
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.fastOutSlowIn].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_positioned.mp4}
///
1089
/// For the animation, you can choose a [curve] as well as a [duration] and the
1090 1091
/// widget will automatically animate to the new target position. If you require
/// more control over the animation (e.g. if you want to stop it mid-animation),
1092 1093
/// consider using a [PositionedTransition] instead, which takes a provided
/// [Animation] as an argument. While that allows you to fine-tune the animation,
1094 1095 1096
/// it also requires more development overhead as you have to manually manage
/// the lifecycle of the underlying [AnimationController].
///
1097
/// {@tool dartpad --template=stateful_widget_scaffold_center}
Anas35's avatar
Anas35 committed
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
///
/// The following example transitions an AnimatedPositioned
/// between two states. It adjusts the `height`, `width`, and
/// [Positioned] properties when tapped.
///
/// ```dart
/// bool selected = false;
///
/// @override
/// Widget build(BuildContext context) {
1108
///   return SizedBox(
1109 1110 1111
///     width: 200,
///     height: 350,
///     child: Stack(
1112
///       children: <Widget>[
1113 1114 1115 1116
///         AnimatedPositioned(
///           width: selected ? 200.0 : 50.0,
///           height: selected ? 50.0 : 200.0,
///           top: selected ? 50.0 : 150.0,
1117
///           duration: const Duration(seconds: 2),
1118 1119 1120 1121 1122 1123 1124 1125 1126
///           curve: Curves.fastOutSlowIn,
///           child: GestureDetector(
///             onTap: () {
///               setState(() {
///                 selected = !selected;
///               });
///             },
///             child: Container(
///               color: Colors.blue,
1127
///               child: const Center(child: Text('Tap me')),
1128
///             ),
Anas35's avatar
Anas35 committed
1129 1130
///           ),
///         ),
1131 1132
///       ],
///     ),
Anas35's avatar
Anas35 committed
1133 1134 1135 1136 1137
///   );
/// }
///```
/// {@end-tool}
///
1138 1139 1140
/// See also:
///
///  * [AnimatedPositionedDirectional], which adapts to the ambient
1141 1142
///    [Directionality] (the same as this widget, but for animating
///    [PositionedDirectional]).
1143
class AnimatedPositioned extends ImplicitlyAnimatedWidget {
1144 1145 1146 1147 1148 1149 1150 1151
  /// Creates a widget that animates its position implicitly.
  ///
  /// Only two out of the three horizontal values ([left], [right],
  /// [width]), and only two out of the three vertical values ([top],
  /// [bottom], [height]), can be set. In each case, at least one of
  /// the three must be null.
  ///
  /// The [curve] and [duration] arguments must not be null.
1152
  const AnimatedPositioned({
1153 1154
    Key? key,
    required this.child,
Ian Hickson's avatar
Ian Hickson committed
1155 1156 1157 1158 1159 1160
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
1161
    Curve curve = Curves.linear,
1162 1163
    required Duration duration,
    VoidCallback? onEnd,
1164 1165
  }) : assert(left == null || right == null || width == null),
       assert(top == null || bottom == null || height == null),
1166
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
Ian Hickson's avatar
Ian Hickson committed
1167

1168 1169 1170
  /// Creates a widget that animates the rectangle it occupies implicitly.
  ///
  /// The [curve] and [duration] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
1171
  AnimatedPositioned.fromRect({
1172 1173 1174
    Key? key,
    required this.child,
    required Rect rect,
1175
    Curve curve = Curves.linear,
1176 1177
    required Duration duration,
    VoidCallback? onEnd,
Ian Hickson's avatar
Ian Hickson committed
1178 1179 1180 1181 1182 1183
  }) : left = rect.left,
       top = rect.top,
       width = rect.width,
       height = rect.height,
       right = null,
       bottom = null,
1184
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
Ian Hickson's avatar
Ian Hickson committed
1185

1186
  /// The widget below this widget in the tree.
1187
  ///
1188
  /// {@macro flutter.widgets.ProxyWidget.child}
Ian Hickson's avatar
Ian Hickson committed
1189 1190 1191
  final Widget child;

  /// The offset of the child's left edge from the left of the stack.
1192
  final double? left;
Ian Hickson's avatar
Ian Hickson committed
1193 1194

  /// The offset of the child's top edge from the top of the stack.
1195
  final double? top;
Ian Hickson's avatar
Ian Hickson committed
1196 1197

  /// The offset of the child's right edge from the right of the stack.
1198
  final double? right;
Ian Hickson's avatar
Ian Hickson committed
1199 1200

  /// The offset of the child's bottom edge from the bottom of the stack.
1201
  final double? bottom;
Ian Hickson's avatar
Ian Hickson committed
1202 1203 1204

  /// The child's width.
  ///
1205 1206
  /// Only two out of the three horizontal values ([left], [right], [width]) can
  /// be set. The third must be null.
1207
  final double? width;
Ian Hickson's avatar
Ian Hickson committed
1208 1209 1210

  /// The child's height.
  ///
1211 1212
  /// Only two out of the three vertical values ([top], [bottom], [height]) can
  /// be set. The third must be null.
1213
  final double? height;
Ian Hickson's avatar
Ian Hickson committed
1214

1215
  @override
1216
  AnimatedWidgetBaseState<AnimatedPositioned> createState() => _AnimatedPositionedState();
Ian Hickson's avatar
Ian Hickson committed
1217 1218

  @override
1219 1220
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1221 1222 1223 1224 1225 1226
    properties.add(DoubleProperty('left', left, defaultValue: null));
    properties.add(DoubleProperty('top', top, defaultValue: null));
    properties.add(DoubleProperty('right', right, defaultValue: null));
    properties.add(DoubleProperty('bottom', bottom, defaultValue: null));
    properties.add(DoubleProperty('width', width, defaultValue: null));
    properties.add(DoubleProperty('height', height, defaultValue: null));
Ian Hickson's avatar
Ian Hickson committed
1227
  }
Ian Hickson's avatar
Ian Hickson committed
1228 1229 1230
}

class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositioned> {
1231 1232 1233 1234 1235 1236
  Tween<double>? _left;
  Tween<double>? _top;
  Tween<double>? _right;
  Tween<double>? _bottom;
  Tween<double>? _width;
  Tween<double>? _height;
1237

1238
  @override
1239
  void forEachTween(TweenVisitor<dynamic> visitor) {
1240 1241 1242 1243 1244 1245
    _left = visitor(_left, widget.left, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _top = visitor(_top, widget.top, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _right = visitor(_right, widget.right, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _bottom = visitor(_bottom, widget.bottom, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _width = visitor(_width, widget.width, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _height = visitor(_height, widget.height, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
Ian Hickson's avatar
Ian Hickson committed
1246 1247
  }

1248
  @override
Ian Hickson's avatar
Ian Hickson committed
1249
  Widget build(BuildContext context) {
1250
    return Positioned(
1251 1252 1253 1254 1255 1256
      left: _left?.evaluate(animation),
      top: _top?.evaluate(animation),
      right: _right?.evaluate(animation),
      bottom: _bottom?.evaluate(animation),
      width: _width?.evaluate(animation),
      height: _height?.evaluate(animation),
1257
      child: widget.child,
Ian Hickson's avatar
Ian Hickson committed
1258 1259 1260
    );
  }

1261
  @override
1262
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
1263
    super.debugFillProperties(description);
1264 1265 1266 1267 1268 1269
    description.add(ObjectFlagProperty<Tween<double>>.has('left', _left));
    description.add(ObjectFlagProperty<Tween<double>>.has('top', _top));
    description.add(ObjectFlagProperty<Tween<double>>.has('right', _right));
    description.add(ObjectFlagProperty<Tween<double>>.has('bottom', _bottom));
    description.add(ObjectFlagProperty<Tween<double>>.has('width', _width));
    description.add(ObjectFlagProperty<Tween<double>>.has('height', _height));
Ian Hickson's avatar
Ian Hickson committed
1270 1271
  }
}
Ian Hickson's avatar
Ian Hickson committed
1272

1273 1274 1275 1276 1277 1278
/// Animated version of [PositionedDirectional] which automatically transitions
/// the child's position over a given duration whenever the given position
/// changes.
///
/// The ambient [Directionality] is used to determine whether [start] is to the
/// left or to the right.
1279 1280 1281
///
/// Only works if it's the child of a [Stack].
///
1282 1283 1284 1285 1286 1287 1288
/// This widget is a good choice if the _size_ of the child would end up
/// changing as a result of this animation. If the size is intended to remain
/// the same, with only the _position_ changing over time, then consider
/// [SlideTransition] instead. [SlideTransition] only triggers a repaint each
/// frame of the animation, whereas [AnimatedPositionedDirectional] will trigger
/// a relayout as well. ([SlideTransition] is also text-direction-aware.)
///
1289 1290 1291 1292
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.fastOutSlowIn].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_positioned_directional.mp4}
///
1293 1294
/// See also:
///
1295
///  * [AnimatedPositioned], which specifies the widget's position visually (the
1296
///    same as this widget, but for animating [Positioned]).
1297 1298 1299
class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget {
  /// Creates a widget that animates its position implicitly.
  ///
1300 1301 1302
  /// Only two out of the three horizontal values ([start], [end], [width]), and
  /// only two out of the three vertical values ([top], [bottom], [height]), can
  /// be set. In each case, at least one of the three must be null.
1303 1304 1305
  ///
  /// The [curve] and [duration] arguments must not be null.
  const AnimatedPositionedDirectional({
1306 1307
    Key? key,
    required this.child,
1308 1309 1310 1311 1312 1313
    this.start,
    this.top,
    this.end,
    this.bottom,
    this.width,
    this.height,
1314
    Curve curve = Curves.linear,
1315 1316
    required Duration duration,
    VoidCallback? onEnd,
1317 1318
  }) : assert(start == null || end == null || width == null),
       assert(top == null || bottom == null || height == null),
1319
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
1320 1321

  /// The widget below this widget in the tree.
1322
  ///
1323
  /// {@macro flutter.widgets.ProxyWidget.child}
1324 1325 1326
  final Widget child;

  /// The offset of the child's start edge from the start of the stack.
1327
  final double? start;
1328 1329

  /// The offset of the child's top edge from the top of the stack.
1330
  final double? top;
1331 1332

  /// The offset of the child's end edge from the end of the stack.
1333
  final double? end;
1334 1335

  /// The offset of the child's bottom edge from the bottom of the stack.
1336
  final double? bottom;
1337 1338 1339

  /// The child's width.
  ///
1340 1341
  /// Only two out of the three horizontal values ([start], [end], [width]) can
  /// be set. The third must be null.
1342
  final double? width;
1343 1344 1345

  /// The child's height.
  ///
1346 1347
  /// Only two out of the three vertical values ([top], [bottom], [height]) can
  /// be set. The third must be null.
1348
  final double? height;
1349 1350

  @override
1351
  AnimatedWidgetBaseState<AnimatedPositionedDirectional> createState() => _AnimatedPositionedDirectionalState();
1352 1353

  @override
1354 1355
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1356 1357 1358 1359 1360 1361
    properties.add(DoubleProperty('start', start, defaultValue: null));
    properties.add(DoubleProperty('top', top, defaultValue: null));
    properties.add(DoubleProperty('end', end, defaultValue: null));
    properties.add(DoubleProperty('bottom', bottom, defaultValue: null));
    properties.add(DoubleProperty('width', width, defaultValue: null));
    properties.add(DoubleProperty('height', height, defaultValue: null));
1362 1363 1364 1365
  }
}

class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<AnimatedPositionedDirectional> {
1366 1367 1368 1369 1370 1371
  Tween<double>? _start;
  Tween<double>? _top;
  Tween<double>? _end;
  Tween<double>? _bottom;
  Tween<double>? _width;
  Tween<double>? _height;
1372 1373 1374

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
1375 1376 1377 1378 1379 1380
    _start = visitor(_start, widget.start, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _top = visitor(_top, widget.top, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _end = visitor(_end, widget.end, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _bottom = visitor(_bottom, widget.bottom, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _width = visitor(_width, widget.width, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _height = visitor(_height, widget.height, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
1381 1382 1383 1384
  }

  @override
  Widget build(BuildContext context) {
1385
    assert(debugCheckHasDirectionality(context));
1386
    return Positioned.directional(
1387
      textDirection: Directionality.of(context),
1388 1389 1390 1391 1392 1393
      start: _start?.evaluate(animation),
      top: _top?.evaluate(animation),
      end: _end?.evaluate(animation),
      bottom: _bottom?.evaluate(animation),
      width: _width?.evaluate(animation),
      height: _height?.evaluate(animation),
1394
      child: widget.child,
1395 1396 1397 1398 1399 1400
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
1401 1402 1403 1404 1405 1406
    description.add(ObjectFlagProperty<Tween<double>>.has('start', _start));
    description.add(ObjectFlagProperty<Tween<double>>.has('top', _top));
    description.add(ObjectFlagProperty<Tween<double>>.has('end', _end));
    description.add(ObjectFlagProperty<Tween<double>>.has('bottom', _bottom));
    description.add(ObjectFlagProperty<Tween<double>>.has('width', _width));
    description.add(ObjectFlagProperty<Tween<double>>.has('height', _height));
1407 1408 1409
  }
}

Ian Hickson's avatar
Ian Hickson committed
1410 1411 1412
/// Animated version of [Opacity] which automatically transitions the child's
/// opacity over a given duration whenever the given opacity changes.
///
1413 1414
/// {@youtube 560 315 https://www.youtube.com/watch?v=QZAvjqOqiLY}
///
1415 1416
/// Animating an opacity is relatively expensive because it requires painting
/// the child into an intermediate buffer.
1417
///
1418 1419 1420 1421
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.fastOutSlowIn].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_opacity.mp4}
///
1422
/// {@tool snippet}
1423 1424 1425
///
/// ```dart
/// class LogoFade extends StatefulWidget {
1426 1427
///   const LogoFade({Key? key}) : super(key: key);
///
1428
///   @override
1429
///   State<LogoFade> createState() => LogoFadeState();
1430 1431 1432 1433
/// }
///
/// class LogoFadeState extends State<LogoFade> {
///   double opacityLevel = 1.0;
1434
///
1435
///   void _changeOpacity() {
1436 1437 1438 1439 1440
///     setState(() => opacityLevel = opacityLevel == 0 ? 1.0 : 0.0);
///   }
///
///   @override
///   Widget build(BuildContext context) {
1441
///     return Column(
1442
///       mainAxisAlignment: MainAxisAlignment.center,
1443
///       children: <Widget>[
1444
///         AnimatedOpacity(
1445
///           opacity: opacityLevel,
1446 1447
///           duration: const Duration(seconds: 3),
///           child: const FlutterLogo(),
1448
///         ),
1449
///         ElevatedButton(
1450
///           child: const Text('Fade Logo'),
1451 1452 1453 1454 1455 1456 1457
///           onPressed: _changeOpacity,
///         ),
///       ],
///     );
///   }
/// }
/// ```
1458
/// {@end-tool}
1459 1460 1461
///
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
1462 1463
///  * [AnimatedCrossFade], for fading between two children.
///  * [AnimatedSwitcher], for fading between many children in sequence.
1464 1465
///  * [FadeTransition], an explicitly animated version of this widget, where
///    an [Animation] is provided by the caller instead of being built in.
Kate Lovett's avatar
Kate Lovett committed
1466 1467
///  * [SliverAnimatedOpacity], for automatically transitioning a sliver's
///    opacity over a given duration whenever the given opacity changes.
Ian Hickson's avatar
Ian Hickson committed
1468
class AnimatedOpacity extends ImplicitlyAnimatedWidget {
1469 1470 1471 1472
  /// Creates a widget that animates its opacity implicitly.
  ///
  /// The [opacity] argument must not be null and must be between 0.0 and 1.0,
  /// inclusive. The [curve] and [duration] arguments must not be null.
1473
  const AnimatedOpacity({
1474
    Key? key,
Ian Hickson's avatar
Ian Hickson committed
1475
    this.child,
1476
    required this.opacity,
1477
    Curve curve = Curves.linear,
1478 1479
    required Duration duration,
    VoidCallback? onEnd,
1480
    this.alwaysIncludeSemantics = false,
1481
  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
1482
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
Ian Hickson's avatar
Ian Hickson committed
1483 1484

  /// The widget below this widget in the tree.
1485
  ///
1486
  /// {@macro flutter.widgets.ProxyWidget.child}
1487
  final Widget? child;
Ian Hickson's avatar
Ian Hickson committed
1488 1489 1490 1491 1492 1493 1494 1495 1496

  /// The target opacity.
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
  ///
  /// The opacity must not be null.
  final double opacity;

1497 1498 1499 1500 1501 1502 1503 1504 1505 1506
  /// Whether the semantic information of the children is always included.
  ///
  /// Defaults to false.
  ///
  /// When true, regardless of the opacity settings the child semantic
  /// information is exposed as if the widget were fully visible. This is
  /// useful in cases where labels may be hidden during animations that
  /// would otherwise contribute relevant semantics.
  final bool alwaysIncludeSemantics;

Ian Hickson's avatar
Ian Hickson committed
1507
  @override
1508
  ImplicitlyAnimatedWidgetState<AnimatedOpacity> createState() => _AnimatedOpacityState();
Ian Hickson's avatar
Ian Hickson committed
1509 1510

  @override
1511 1512
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1513
    properties.add(DoubleProperty('opacity', opacity));
Ian Hickson's avatar
Ian Hickson committed
1514 1515 1516
  }
}

1517
class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacity> {
1518 1519
  Tween<double>? _opacity;
  late Animation<double> _opacityAnimation;
Ian Hickson's avatar
Ian Hickson committed
1520 1521 1522

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
1523
    _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
Ian Hickson's avatar
Ian Hickson committed
1524 1525
  }

1526 1527
  @override
  void didUpdateTweens() {
1528
    _opacityAnimation = animation.drive(_opacity!);
1529 1530
  }

Ian Hickson's avatar
Ian Hickson committed
1531 1532
  @override
  Widget build(BuildContext context) {
1533
    return FadeTransition(
1534
      opacity: _opacityAnimation,
1535
      alwaysIncludeSemantics: widget.alwaysIncludeSemantics,
1536
      child: widget.child,
Ian Hickson's avatar
Ian Hickson committed
1537 1538 1539
    );
  }
}
1540

Kate Lovett's avatar
Kate Lovett committed
1541 1542 1543 1544 1545 1546 1547 1548 1549 1550
/// Animated version of [SliverOpacity] which automatically transitions the
/// sliver child's opacity over a given duration whenever the given opacity
/// changes.
///
/// Animating an opacity is relatively expensive because it requires painting
/// the sliver child into an intermediate buffer.
///
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.fastOutSlowIn].
///
1551
/// {@tool dartpad --template=stateful_widget_scaffold_center_freeform_state}
Kate Lovett's avatar
Kate Lovett committed
1552 1553 1554 1555 1556 1557 1558
/// Creates a [CustomScrollView] with a [SliverFixedExtentList] and a
/// [FloatingActionButton]. Pressing the button animates the lists' opacity.
///
/// ```dart
/// class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
///   bool _visible = true;
///
1559
///   @override
Kate Lovett's avatar
Kate Lovett committed
1560 1561 1562 1563 1564
///   Widget build(BuildContext context) {
///     return CustomScrollView(
///       slivers: <Widget>[
///         SliverAnimatedOpacity(
///           opacity: _visible ? 1.0 : 0.0,
1565
///           duration: const Duration(milliseconds: 500),
Kate Lovett's avatar
Kate Lovett committed
1566 1567 1568 1569 1570
///           sliver: SliverFixedExtentList(
///             itemExtent: 100.0,
///             delegate: SliverChildBuilderDelegate(
///               (BuildContext context, int index) {
///                 return Container(
1571
///                   color: index.isEven
Kate Lovett's avatar
Kate Lovett committed
1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587
///                     ? Colors.indigo[200]
///                     : Colors.orange[200],
///                 );
///               },
///               childCount: 5,
///             ),
///           ),
///         ),
///         SliverToBoxAdapter(
///           child: FloatingActionButton(
///             onPressed: () {
///               setState(() {
///                 _visible = !_visible;
///               });
///             },
///             tooltip: 'Toggle opacity',
1588
///             child: const Icon(Icons.flip),
Kate Lovett's avatar
Kate Lovett committed
1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609
///           )
///         ),
///       ]
///     );
///   }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
///  * [SliverFadeTransition], an explicitly animated version of this widget, where
///    an [Animation] is provided by the caller instead of being built in.
///  * [AnimatedOpacity], for automatically transitioning a box child's
///    opacity over a given duration whenever the given opacity changes.
class SliverAnimatedOpacity extends ImplicitlyAnimatedWidget {
  /// Creates a widget that animates its opacity implicitly.
  ///
  /// The [opacity] argument must not be null and must be between 0.0 and 1.0,
  /// inclusive. The [curve] and [duration] arguments must not be null.
  const SliverAnimatedOpacity({
1610
    Key? key,
Kate Lovett's avatar
Kate Lovett committed
1611
    this.sliver,
1612
    required this.opacity,
Kate Lovett's avatar
Kate Lovett committed
1613
    Curve curve = Curves.linear,
1614 1615
    required Duration duration,
    VoidCallback? onEnd,
Kate Lovett's avatar
Kate Lovett committed
1616 1617 1618 1619 1620
    this.alwaysIncludeSemantics = false,
  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
      super(key: key, curve: curve, duration: duration, onEnd: onEnd);

  /// The sliver below this widget in the tree.
1621
  final Widget? sliver;
Kate Lovett's avatar
Kate Lovett committed
1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641

  /// The target opacity.
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
  ///
  /// The opacity must not be null.
  final double opacity;

  /// Whether the semantic information of the children is always included.
  ///
  /// Defaults to false.
  ///
  /// When true, regardless of the opacity settings the sliver child's semantic
  /// information is exposed as if the widget were fully visible. This is
  /// useful in cases where labels may be hidden during animations that
  /// would otherwise contribute relevant semantics.
  final bool alwaysIncludeSemantics;

  @override
1642
  ImplicitlyAnimatedWidgetState<SliverAnimatedOpacity> createState() => _SliverAnimatedOpacityState();
Kate Lovett's avatar
Kate Lovett committed
1643 1644 1645 1646 1647 1648 1649 1650 1651

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('opacity', opacity));
  }
}

class _SliverAnimatedOpacityState extends ImplicitlyAnimatedWidgetState<SliverAnimatedOpacity> {
1652 1653
  Tween<double>? _opacity;
  late Animation<double> _opacityAnimation;
Kate Lovett's avatar
Kate Lovett committed
1654 1655 1656

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
1657
    _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
Kate Lovett's avatar
Kate Lovett committed
1658 1659 1660 1661
  }

  @override
  void didUpdateTweens() {
1662
    _opacityAnimation = animation.drive(_opacity!);
Kate Lovett's avatar
Kate Lovett committed
1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674
  }

  @override
  Widget build(BuildContext context) {
    return SliverFadeTransition(
      opacity: _opacityAnimation,
      sliver: widget.sliver,
      alwaysIncludeSemantics: widget.alwaysIncludeSemantics,
    );
  }
}

1675 1676 1677 1678
/// Animated version of [DefaultTextStyle] which automatically transitions the
/// default text style (the text style to apply to descendant [Text] widgets
/// without explicit style) over a given duration whenever the given style
/// changes.
1679
///
1680
/// The [textAlign], [softWrap], [overflow], [maxLines], [textWidthBasis]
1681 1682
/// and [textHeightBehavior] properties are not animated and take effect
/// immediately when changed.
1683 1684 1685 1686
///
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.elasticInOut].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_default_text_style.mp4}
1687
///
1688
/// For the animation, you can choose a [curve] as well as a [duration] and the
1689 1690
/// widget will automatically animate to the new default text style. If you require
/// more control over the animation (e.g. if you want to stop it mid-animation),
1691
/// consider using a [DefaultTextStyleTransition] instead, which takes a provided
1692 1693 1694
/// [Animation] as argument. While that allows you to fine-tune the animation,
/// it also requires more development overhead as you have to manually manage
/// the lifecycle of the underlying [AnimationController].
1695
class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget {
1696 1697
  /// Creates a widget that animates the default text style implicitly.
  ///
1698 1699
  /// The [child], [style], [softWrap], [overflow], [curve], and [duration]
  /// arguments must not be null.
1700
  const AnimatedDefaultTextStyle({
1701 1702 1703
    Key? key,
    required this.child,
    required this.style,
1704
    this.textAlign,
1705 1706
    this.softWrap = true,
    this.overflow = TextOverflow.clip,
1707
    this.maxLines,
1708 1709
    this.textWidthBasis = TextWidthBasis.parent,
    this.textHeightBehavior,
1710
    Curve curve = Curves.linear,
1711 1712
    required Duration duration,
    VoidCallback? onEnd,
1713 1714
  }) : assert(style != null),
       assert(child != null),
1715 1716 1717
       assert(softWrap != null),
       assert(overflow != null),
       assert(maxLines == null || maxLines > 0),
1718
       assert(textWidthBasis != null),
1719
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
1720 1721

  /// The widget below this widget in the tree.
1722
  ///
1723
  /// {@macro flutter.widgets.ProxyWidget.child}
1724 1725 1726 1727 1728
  final Widget child;

  /// The target text style.
  ///
  /// The text style must not be null.
1729 1730
  ///
  /// When this property is changed, the style will be animated over [duration] time.
1731 1732
  final TextStyle style;

1733 1734 1735
  /// How the text should be aligned horizontally.
  ///
  /// This property takes effect immediately when changed, it is not animated.
1736
  final TextAlign? textAlign;
1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754

  /// Whether the text should break at soft line breaks.
  ///
  /// This property takes effect immediately when changed, it is not animated.
  ///
  /// See [DefaultTextStyle.softWrap] for more details.
  final bool softWrap;

  /// How visual overflow should be handled.
  ///
  /// This property takes effect immediately when changed, it is not animated.
  final TextOverflow overflow;

  /// An optional maximum number of lines for the text to span, wrapping if necessary.
  ///
  /// This property takes effect immediately when changed, it is not animated.
  ///
  /// See [DefaultTextStyle.maxLines] for more details.
1755
  final int? maxLines;
1756

1757 1758 1759
  /// The strategy to use when calculating the width of the Text.
  ///
  /// See [TextWidthBasis] for possible values and their implications.
1760 1761 1762
  final TextWidthBasis textWidthBasis;

  /// {@macro flutter.dart:ui.textHeightBehavior}
1763
  final ui.TextHeightBehavior? textHeightBehavior;
1764

1765
  @override
1766
  AnimatedWidgetBaseState<AnimatedDefaultTextStyle> createState() => _AnimatedDefaultTextStyleState();
1767 1768

  @override
1769 1770
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1771
    style.debugFillProperties(properties);
1772 1773 1774 1775
    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
1776 1777
    properties.add(EnumProperty<TextWidthBasis>('textWidthBasis', textWidthBasis, defaultValue: TextWidthBasis.parent));
    properties.add(DiagnosticsProperty<ui.TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
1778 1779 1780 1781
  }
}

class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDefaultTextStyle> {
1782
  TextStyleTween? _style;
1783 1784 1785

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
1786
    _style = visitor(_style, widget.style, (dynamic value) => TextStyleTween(begin: value as TextStyle)) as TextStyleTween?;
1787 1788 1789 1790
  }

  @override
  Widget build(BuildContext context) {
1791
    return DefaultTextStyle(
1792
      style: _style!.evaluate(animation),
1793 1794 1795 1796
      textAlign: widget.textAlign,
      softWrap: widget.softWrap,
      overflow: widget.overflow,
      maxLines: widget.maxLines,
1797 1798
      textWidthBasis: widget.textWidthBasis,
      textHeightBehavior: widget.textHeightBehavior,
1799
      child: widget.child,
1800 1801 1802
    );
  }
}
1803 1804

/// Animated version of [PhysicalModel].
1805 1806 1807 1808 1809 1810 1811 1812 1813
///
/// The [borderRadius] and [elevation] are animated.
///
/// The [color] is animated if the [animateColor] property is set; otherwise,
/// the color changes immediately at the start of the animation for the other
/// two properties. This allows the color to be animated independently (e.g.
/// because it is being driven by an [AnimatedTheme]).
///
/// The [shape] is not animated.
1814 1815 1816 1817
///
/// Here's an illustration of what using this widget looks like, using a [curve]
/// of [Curves.fastOutSlowIn].
/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_physical_model.mp4}
1818 1819 1820
class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
  /// Creates a widget that animates the properties of a [PhysicalModel].
  ///
1821 1822 1823
  /// The [child], [shape], [borderRadius], [elevation], [color], [shadowColor],
  /// [curve], [clipBehavior], and [duration] arguments must not be null.
  /// Additionally, [elevation] must be non-negative.
1824 1825
  ///
  /// Animating [color] is optional and is controlled by the [animateColor] flag.
1826 1827
  ///
  /// Animating [shadowColor] is optional and is controlled by the [animateShadowColor] flag.
1828
  const AnimatedPhysicalModel({
1829 1830 1831
    Key? key,
    required this.child,
    required this.shape,
1832
    this.clipBehavior = Clip.none,
1833
    this.borderRadius = BorderRadius.zero,
1834 1835
    required this.elevation,
    required this.color,
1836
    this.animateColor = true,
1837
    required this.shadowColor,
1838 1839
    this.animateShadowColor = true,
    Curve curve = Curves.linear,
1840 1841
    required Duration duration,
    VoidCallback? onEnd,
1842 1843
  }) : assert(child != null),
       assert(shape != null),
1844
       assert(clipBehavior != null),
1845
       assert(borderRadius != null),
1846
       assert(elevation != null && elevation >= 0.0),
1847
       assert(color != null),
1848 1849 1850
       assert(shadowColor != null),
       assert(animateColor != null),
       assert(animateShadowColor != null),
1851
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
1852 1853

  /// The widget below this widget in the tree.
1854
  ///
1855
  /// {@macro flutter.widgets.ProxyWidget.child}
1856 1857 1858 1859 1860 1861 1862
  final Widget child;

  /// The type of shape.
  ///
  /// This property is not animated.
  final BoxShape shape;

1863
  /// {@macro flutter.material.Material.clipBehavior}
1864 1865
  ///
  /// Defaults to [Clip.none].
1866 1867
  final Clip clipBehavior;

1868 1869 1870
  /// The target border radius of the rounded corners for a rectangle shape.
  final BorderRadius borderRadius;

1871 1872 1873 1874
  /// The target z-coordinate relative to the parent at which to place this
  /// physical object.
  ///
  /// The value will always be non-negative.
1875 1876 1877 1878 1879 1880 1881 1882
  final double elevation;

  /// The target background color.
  final Color color;

  /// Whether the color should be animated.
  final bool animateColor;

1883 1884 1885 1886 1887 1888
  /// The target shadow color.
  final Color shadowColor;

  /// Whether the shadow color should be animated.
  final bool animateShadowColor;

1889
  @override
1890
  AnimatedWidgetBaseState<AnimatedPhysicalModel> createState() => _AnimatedPhysicalModelState();
1891 1892

  @override
1893 1894
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1895 1896 1897
    properties.add(EnumProperty<BoxShape>('shape', shape));
    properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
    properties.add(DoubleProperty('elevation', elevation));
1898
    properties.add(ColorProperty('color', color));
1899
    properties.add(DiagnosticsProperty<bool>('animateColor', animateColor));
1900
    properties.add(ColorProperty('shadowColor', shadowColor));
1901
    properties.add(DiagnosticsProperty<bool>('animateShadowColor', animateShadowColor));
1902 1903 1904 1905
  }
}

class _AnimatedPhysicalModelState extends AnimatedWidgetBaseState<AnimatedPhysicalModel> {
1906 1907 1908 1909
  BorderRadiusTween? _borderRadius;
  Tween<double>? _elevation;
  ColorTween? _color;
  ColorTween? _shadowColor;
1910 1911 1912

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
1913 1914 1915 1916
    _borderRadius = visitor(_borderRadius, widget.borderRadius, (dynamic value) => BorderRadiusTween(begin: value as BorderRadius)) as BorderRadiusTween?;
    _elevation = visitor(_elevation, widget.elevation, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
    _color = visitor(_color, widget.color, (dynamic value) => ColorTween(begin: value as Color)) as ColorTween?;
    _shadowColor = visitor(_shadowColor, widget.shadowColor, (dynamic value) => ColorTween(begin: value as Color)) as ColorTween?;
1917 1918 1919 1920
  }

  @override
  Widget build(BuildContext context) {
1921
    return PhysicalModel(
1922
      shape: widget.shape,
1923
      clipBehavior: widget.clipBehavior,
1924 1925 1926
      borderRadius: _borderRadius!.evaluate(animation),
      elevation: _elevation!.evaluate(animation),
      color: widget.animateColor ? _color!.evaluate(animation)! : widget.color,
1927
      shadowColor: widget.animateShadowColor
1928
          ? _shadowColor!.evaluate(animation)!
1929
          : widget.shadowColor,
1930
      child: widget.child,
1931 1932 1933
    );
  }
}