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