| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'package:flutter/foundation.dart'; |
| 6 | /// @docImport 'package:flutter/material.dart'; |
| 7 | /// |
| 8 | /// @docImport 'animated_size.dart'; |
| 9 | /// @docImport 'transitions.dart'; |
| 10 | library; |
| 11 | |
| 12 | import 'package:flutter/animation.dart'; |
| 13 | |
| 14 | import 'framework.dart'; |
| 15 | import 'implicit_animations.dart'; |
| 16 | import 'value_listenable_builder.dart'; |
| 17 | |
| 18 | /// [Widget] builder that animates a property of a [Widget] to a target value |
| 19 | /// whenever the target value changes. |
| 20 | /// |
| 21 | /// {@youtube 560 315 https://www.youtube.com/watch?v=l9uHB8VXZOg} |
| 22 | /// |
| 23 | /// The type of the animated property ([Color], [Rect], [double], etc.) is |
| 24 | /// defined via the type of the provided [tween] (e.g. [ColorTween], |
| 25 | /// [RectTween], [Tween<double>], etc.). |
| 26 | /// |
| 27 | /// The [tween] also defines the target value for the animation: When the widget |
| 28 | /// first builds, it animates from [Tween.begin] to [Tween.end]. A new animation |
| 29 | /// can be triggered anytime by providing a new [tween] with a new [Tween.end] |
| 30 | /// value. The new animation runs from the current animation value (which may be |
| 31 | /// [Tween.end] of the old [tween], if that animation completed) to [Tween.end] |
| 32 | /// of the new [tween]. |
| 33 | /// |
| 34 | /// The animation is further customized by providing a [curve] and [duration]. |
| 35 | /// |
| 36 | /// The current value of the animation along with the [child] is passed to |
| 37 | /// the [builder] callback, which is expected to build a [Widget] based on the |
| 38 | /// current animation value. The [builder] is called throughout the animation |
| 39 | /// for every animation value until [Tween.end] is reached. |
| 40 | /// |
| 41 | /// A provided [onEnd] callback is called whenever an animation completes. |
| 42 | /// Registering an [onEnd] callback my be useful to trigger an action (like |
| 43 | /// another animation) at the end of the current animation. |
| 44 | /// |
| 45 | /// ## Performance optimizations |
| 46 | /// |
| 47 | /// If your [builder] function contains a subtree that does not depend on the |
| 48 | /// animation, it's more efficient to build that subtree once instead of |
| 49 | /// rebuilding it on every animation tick. |
| 50 | /// |
| 51 | /// If you pass the pre-built subtree as the [child] parameter, the |
| 52 | /// AnimatedBuilder will pass it back to your builder function so that you |
| 53 | /// can incorporate it into your build. |
| 54 | /// |
| 55 | /// Using this pre-built child is entirely optional, but can improve |
| 56 | /// performance significantly in some cases and is therefore a good practice. |
| 57 | /// |
| 58 | /// ## Ownership of the [Tween] |
| 59 | /// |
| 60 | /// The [TweenAnimationBuilder] takes full ownership of the provided [tween] |
| 61 | /// instance and it will mutate it. Once a [Tween] has been passed to a |
| 62 | /// [TweenAnimationBuilder], its properties should not be accessed or changed |
| 63 | /// anymore to avoid interference with the [TweenAnimationBuilder]. |
| 64 | /// |
| 65 | /// It is good practice to never store a [Tween] provided to a |
| 66 | /// [TweenAnimationBuilder] in an instance variable to avoid accidental |
| 67 | /// modifications of the [Tween]. |
| 68 | /// |
| 69 | /// ## Example Code |
| 70 | /// |
| 71 | /// {@tool dartpad} |
| 72 | /// This example shows an [IconButton] that "zooms" in when the widget first |
| 73 | /// builds (its size smoothly increases from 0 to 24) and whenever the button |
| 74 | /// is pressed, it smoothly changes its size to the new target value of either |
| 75 | /// 48 or 24. |
| 76 | /// |
| 77 | /// ** See code in examples/api/lib/widgets/tween_animation_builder/tween_animation_builder.0.dart ** |
| 78 | /// {@end-tool} |
| 79 | /// |
| 80 | /// ## Relationship to [ImplicitlyAnimatedWidget]s and [AnimatedWidget]s |
| 81 | /// |
| 82 | /// The [ImplicitlyAnimatedWidget] has many subclasses that provide animated |
| 83 | /// versions of regular widgets. These subclasses (like [AnimatedOpacity], |
| 84 | /// [AnimatedContainer], [AnimatedSize], etc.) animate changes in their |
| 85 | /// properties smoothly and they are easier to use than this general-purpose |
| 86 | /// builder. However, [TweenAnimationBuilder] (which itself is a subclass of |
| 87 | /// [ImplicitlyAnimatedWidget]) is handy for animating any widget property to a |
| 88 | /// given target value even when the framework (or third-party widget library) |
| 89 | /// doesn't ship with an animated version of that widget. |
| 90 | /// |
| 91 | /// Those [ImplicitlyAnimatedWidget]s (including this [TweenAnimationBuilder]) |
| 92 | /// all manage an internal [AnimationController] to drive the animation. If you |
| 93 | /// want more control over the animation than just setting a target value, |
| 94 | /// [duration], and [curve], have a look at (subclasses of) [AnimatedWidget]s. |
| 95 | /// For those, you have to manually manage an [AnimationController] giving you |
| 96 | /// full control over the animation. An example of an [AnimatedWidget] is the |
| 97 | /// [AnimatedBuilder], which can be used similarly to this |
| 98 | /// [TweenAnimationBuilder], but unlike the latter it is powered by a |
| 99 | /// developer-managed [AnimationController]. |
| 100 | /// |
| 101 | /// See also: |
| 102 | /// |
| 103 | /// * [ValueListenableBuilder], a widget whose content stays synced with a |
| 104 | /// [ValueListenable] instead of a [Tween]. |
| 105 | class TweenAnimationBuilder<T extends Object?> extends ImplicitlyAnimatedWidget { |
| 106 | /// Creates a [TweenAnimationBuilder]. |
| 107 | /// |
| 108 | /// The [TweenAnimationBuilder] takes full ownership of the provided [tween] |
| 109 | /// instance and mutates it. Once a [Tween] has been passed to a |
| 110 | /// [TweenAnimationBuilder], its properties should not be accessed or changed |
| 111 | /// anymore to avoid interference with the [TweenAnimationBuilder]. |
| 112 | const TweenAnimationBuilder({ |
| 113 | super.key, |
| 114 | required this.tween, |
| 115 | required super.duration, |
| 116 | super.curve, |
| 117 | required this.builder, |
| 118 | super.onEnd, |
| 119 | this.child, |
| 120 | }); |
| 121 | |
| 122 | /// Defines the target value for the animation. |
| 123 | /// |
| 124 | /// When the widget first builds, the animation runs from [Tween.begin] to |
| 125 | /// [Tween.end], if [Tween.begin] is non-null. A new animation can be |
| 126 | /// triggered at anytime by providing a new [Tween] with a new [Tween.end] |
| 127 | /// value. The new animation runs from the current animation value (which may |
| 128 | /// be [Tween.end] of the old [tween], if that animation completed) to |
| 129 | /// [Tween.end] of the new [tween]. The [Tween.begin] value is ignored except |
| 130 | /// for the initial animation that is triggered when the widget builds for the |
| 131 | /// first time. |
| 132 | /// |
| 133 | /// Any (subclass of) [Tween] is accepted as an argument. For example, to |
| 134 | /// animate the height or width of a [Widget], use a [Tween<double>], or |
| 135 | /// check out the [ColorTween] to animate the color property of a [Widget]. |
| 136 | /// |
| 137 | /// Any [Tween] provided must have a non-null [Tween.end] value. |
| 138 | /// |
| 139 | /// ## Ownership |
| 140 | /// |
| 141 | /// The [TweenAnimationBuilder] takes full ownership of the provided [Tween] |
| 142 | /// and it will mutate the [Tween]. Once a [Tween] instance has been passed |
| 143 | /// to [TweenAnimationBuilder] its properties should not be accessed or |
| 144 | /// changed anymore to avoid any interference with the |
| 145 | /// [TweenAnimationBuilder]. If you need to change the [Tween], create a |
| 146 | /// **new instance** with the new values. |
| 147 | /// |
| 148 | /// It is good practice to never store a [Tween] provided to a |
| 149 | /// [TweenAnimationBuilder] in an instance variable to avoid accidental |
| 150 | /// modifications of the [Tween]. |
| 151 | final Tween<T> tween; |
| 152 | |
| 153 | /// Called every time the animation value changes. |
| 154 | /// |
| 155 | /// The current animation value is passed to the builder along with the |
| 156 | /// [child]. The builder should build a [Widget] based on the current |
| 157 | /// animation value and incorporate the [child] into it, if it is non-null. |
| 158 | final ValueWidgetBuilder<T> builder; |
| 159 | |
| 160 | /// The child widget to pass to the builder. |
| 161 | /// |
| 162 | /// If a builder callback's return value contains a subtree that does not |
| 163 | /// depend on the animation, it's more efficient to build that subtree once |
| 164 | /// instead of rebuilding it on every animation tick. |
| 165 | /// |
| 166 | /// If the pre-built subtree is passed as the child parameter, the |
| 167 | /// [TweenAnimationBuilder] will pass it back to the [builder] function so |
| 168 | /// that it can be incorporated into the build. |
| 169 | /// |
| 170 | /// Using this pre-built child is entirely optional, but can improve |
| 171 | /// performance significantly in some cases and is therefore a good practice. |
| 172 | final Widget? child; |
| 173 | |
| 174 | @override |
| 175 | ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() { |
| 176 | return _TweenAnimationBuilderState<T>(); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | class _TweenAnimationBuilderState<T extends Object?> |
| 181 | extends AnimatedWidgetBaseState<TweenAnimationBuilder<T>> { |
| 182 | Tween<T>? _currentTween; |
| 183 | |
| 184 | @override |
| 185 | void initState() { |
| 186 | _currentTween = widget.tween; |
| 187 | _currentTween!.begin ??= _currentTween!.end; |
| 188 | super.initState(); |
| 189 | if (_currentTween!.begin != _currentTween!.end) { |
| 190 | controller.forward(); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | @override |
| 195 | void forEachTween(TweenVisitor<dynamic> visitor) { |
| 196 | assert( |
| 197 | widget.tween.end != null, |
| 198 | 'Tween provided to TweenAnimationBuilder must have non-null Tween.end value.' , |
| 199 | ); |
| 200 | _currentTween = |
| 201 | visitor(_currentTween, widget.tween.end, (dynamic value) { |
| 202 | assert(false); |
| 203 | throw StateError( |
| 204 | 'Constructor will never be called because null is never provided as current tween.' , |
| 205 | ); |
| 206 | }) |
| 207 | as Tween<T>?; |
| 208 | } |
| 209 | |
| 210 | @override |
| 211 | Widget build(BuildContext context) { |
| 212 | return widget.builder(context, _currentTween!.evaluate(animation), widget.child); |
| 213 | } |
| 214 | } |
| 215 | |