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?> extends AnimatedWidgetBaseState<TweenAnimationBuilder<T>> { |
181 | Tween<T>? _currentTween; |
182 | |
183 | @override |
184 | void initState() { |
185 | _currentTween = widget.tween; |
186 | _currentTween!.begin ??= _currentTween!.end; |
187 | super.initState(); |
188 | if (_currentTween!.begin != _currentTween!.end) { |
189 | controller.forward(); |
190 | } |
191 | } |
192 | |
193 | @override |
194 | void forEachTween(TweenVisitor<dynamic> visitor) { |
195 | assert( |
196 | widget.tween.end != null, |
197 | 'Tween provided to TweenAnimationBuilder must have non-null Tween.end value.' , |
198 | ); |
199 | _currentTween = visitor(_currentTween, widget.tween.end, (dynamic value) { |
200 | assert(false); |
201 | throw StateError('Constructor will never be called because null is never provided as current tween.' ); |
202 | }) as Tween<T>?; |
203 | } |
204 | |
205 | @override |
206 | Widget build(BuildContext context) { |
207 | return widget.builder(context, _currentTween!.evaluate(animation), widget.child); |
208 | } |
209 | } |
210 | |