1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:math' as math; |
6 | |
7 | import 'package:flutter/rendering.dart'; |
8 | |
9 | import 'basic.dart'; |
10 | import 'container.dart'; |
11 | import 'framework.dart'; |
12 | import 'text.dart'; |
13 | |
14 | export 'package:flutter/rendering.dart' show RelativeRect; |
15 | |
16 | /// A widget that rebuilds when the given [Listenable] changes value. |
17 | /// |
18 | /// {@youtube 560 315 https://www.youtube.com/watch?v=LKKgYpC-EPQ} |
19 | /// |
20 | /// [AnimatedWidget] is most commonly used with [Animation] objects, which are |
21 | /// [Listenable], but it can be used with any [Listenable], including |
22 | /// [ChangeNotifier] and [ValueNotifier]. |
23 | /// |
24 | /// [AnimatedWidget] is most useful for widgets that are otherwise stateless. To |
25 | /// use [AnimatedWidget], subclass it and implement the build function. |
26 | /// |
27 | /// {@tool dartpad} |
28 | /// This code defines a widget called `Spinner` that spins a green square |
29 | /// continually. It is built with an [AnimatedWidget]. |
30 | /// |
31 | /// ** See code in examples/api/lib/widgets/transitions/animated_widget.0.dart ** |
32 | /// {@end-tool} |
33 | /// |
34 | /// For more complex case involving additional state, consider using |
35 | /// [AnimatedBuilder] or [ListenableBuilder]. |
36 | /// |
37 | /// ## Relationship to [ImplicitlyAnimatedWidget]s |
38 | /// |
39 | /// [AnimatedWidget]s (and their subclasses) take an explicit [Listenable] as |
40 | /// argument, which is usually an [Animation] derived from an |
41 | /// [AnimationController]. In most cases, the lifecycle of that |
42 | /// [AnimationController] has to be managed manually by the developer. |
43 | /// In contrast to that, [ImplicitlyAnimatedWidget]s (and their subclasses) |
44 | /// automatically manage their own internal [AnimationController] making those |
45 | /// classes easier to use as no external [Animation] has to be provided by the |
46 | /// developer. If you only need to set a target value for the animation and |
47 | /// configure its duration/curve, consider using (a subclass of) |
48 | /// [ImplicitlyAnimatedWidget]s instead of (a subclass of) this class. |
49 | /// |
50 | /// ## Common animated widgets |
51 | /// |
52 | /// A number of animated widgets ship with the framework. They are usually named |
53 | /// `FooTransition`, where `Foo` is the name of the non-animated |
54 | /// version of that widget. The subclasses of this class should not be confused |
55 | /// with subclasses of [ImplicitlyAnimatedWidget] (see above), which are usually |
56 | /// named `AnimatedFoo`. Commonly used animated widgets include: |
57 | /// |
58 | /// * [ListenableBuilder], which uses a builder pattern that is useful for |
59 | /// complex [Listenable] use cases. |
60 | /// * [AnimatedBuilder], which uses a builder pattern that is useful for |
61 | /// complex [Animation] use cases. |
62 | /// * [AlignTransition], which is an animated version of [Align]. |
63 | /// * [DecoratedBoxTransition], which is an animated version of [DecoratedBox]. |
64 | /// * [DefaultTextStyleTransition], which is an animated version of |
65 | /// [DefaultTextStyle]. |
66 | /// * [PositionedTransition], which is an animated version of [Positioned]. |
67 | /// * [RelativePositionedTransition], which is an animated version of |
68 | /// [Positioned]. |
69 | /// * [RotationTransition], which animates the rotation of a widget. |
70 | /// * [ScaleTransition], which animates the scale of a widget. |
71 | /// * [SizeTransition], which animates its own size. |
72 | /// * [SlideTransition], which animates the position of a widget relative to |
73 | /// its normal position. |
74 | /// * [FadeTransition], which is an animated version of [Opacity]. |
75 | /// * [AnimatedModalBarrier], which is an animated version of [ModalBarrier]. |
76 | abstract class AnimatedWidget extends StatefulWidget { |
77 | /// Creates a widget that rebuilds when the given listenable changes. |
78 | /// |
79 | /// The [listenable] argument is required. |
80 | const AnimatedWidget({ |
81 | super.key, |
82 | required this.listenable, |
83 | }); |
84 | |
85 | /// The [Listenable] to which this widget is listening. |
86 | /// |
87 | /// Commonly an [Animation] or a [ChangeNotifier]. |
88 | final Listenable listenable; |
89 | |
90 | /// Override this method to build widgets that depend on the state of the |
91 | /// listenable (e.g., the current value of the animation). |
92 | @protected |
93 | Widget build(BuildContext context); |
94 | |
95 | /// Subclasses typically do not override this method. |
96 | @override |
97 | State<AnimatedWidget> createState() => _AnimatedState(); |
98 | |
99 | @override |
100 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
101 | super.debugFillProperties(properties); |
102 | properties.add(DiagnosticsProperty<Listenable>('listenable' , listenable)); |
103 | } |
104 | } |
105 | |
106 | class _AnimatedState extends State<AnimatedWidget> { |
107 | @override |
108 | void initState() { |
109 | super.initState(); |
110 | widget.listenable.addListener(_handleChange); |
111 | } |
112 | |
113 | @override |
114 | void didUpdateWidget(AnimatedWidget oldWidget) { |
115 | super.didUpdateWidget(oldWidget); |
116 | if (widget.listenable != oldWidget.listenable) { |
117 | oldWidget.listenable.removeListener(_handleChange); |
118 | widget.listenable.addListener(_handleChange); |
119 | } |
120 | } |
121 | |
122 | @override |
123 | void dispose() { |
124 | widget.listenable.removeListener(_handleChange); |
125 | super.dispose(); |
126 | } |
127 | |
128 | void _handleChange() { |
129 | setState(() { |
130 | // The listenable's state is our build state, and it changed already. |
131 | }); |
132 | } |
133 | |
134 | @override |
135 | Widget build(BuildContext context) => widget.build(context); |
136 | } |
137 | |
138 | /// Animates the position of a widget relative to its normal position. |
139 | /// |
140 | /// The translation is expressed as an [Offset] scaled to the child's size. For |
141 | /// example, an [Offset] with a `dx` of 0.25 will result in a horizontal |
142 | /// translation of one quarter the width of the child. |
143 | /// |
144 | /// By default, the offsets are applied in the coordinate system of the canvas |
145 | /// (so positive x offsets move the child towards the right). If a |
146 | /// [textDirection] is provided, then the offsets are applied in the reading |
147 | /// direction, so in right-to-left text, positive x offsets move towards the |
148 | /// left, and in left-to-right text, positive x offsets move towards the right. |
149 | /// |
150 | /// Here's an illustration of the [SlideTransition] widget, with its [position] |
151 | /// animated by a [CurvedAnimation] set to [Curves.elasticIn]: |
152 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/slide_transition.mp4} |
153 | /// |
154 | /// {@tool dartpad} |
155 | /// The following code implements the [SlideTransition] as seen in the video |
156 | /// above: |
157 | /// |
158 | /// ** See code in examples/api/lib/widgets/transitions/slide_transition.0.dart ** |
159 | /// {@end-tool} |
160 | /// |
161 | /// See also: |
162 | /// |
163 | /// * [AlignTransition], an animated version of an [Align] that animates its |
164 | /// [Align.alignment] property. |
165 | /// * [PositionedTransition], a widget that animates its child from a start |
166 | /// position to an end position over the lifetime of the animation. |
167 | /// * [RelativePositionedTransition], a widget that transitions its child's |
168 | /// position based on the value of a rectangle relative to a bounding box. |
169 | class SlideTransition extends AnimatedWidget { |
170 | /// Creates a fractional translation transition. |
171 | const SlideTransition({ |
172 | super.key, |
173 | required Animation<Offset> position, |
174 | this.transformHitTests = true, |
175 | this.textDirection, |
176 | this.child, |
177 | }) : super(listenable: position); |
178 | |
179 | /// The animation that controls the position of the child. |
180 | /// |
181 | /// If the current value of the position animation is `(dx, dy)`, the child |
182 | /// will be translated horizontally by `width * dx` and vertically by |
183 | /// `height * dy`, after applying the [textDirection] if available. |
184 | Animation<Offset> get position => listenable as Animation<Offset>; |
185 | |
186 | /// The direction to use for the x offset described by the [position]. |
187 | /// |
188 | /// If [textDirection] is null, the x offset is applied in the coordinate |
189 | /// system of the canvas (so positive x offsets move the child towards the |
190 | /// right). |
191 | /// |
192 | /// If [textDirection] is [TextDirection.rtl], the x offset is applied in the |
193 | /// reading direction such that x offsets move the child towards the left. |
194 | /// |
195 | /// If [textDirection] is [TextDirection.ltr], the x offset is applied in the |
196 | /// reading direction such that x offsets move the child towards the right. |
197 | final TextDirection? textDirection; |
198 | |
199 | /// Whether hit testing should be affected by the slide animation. |
200 | /// |
201 | /// If false, hit testing will proceed as if the child was not translated at |
202 | /// all. Setting this value to false is useful for fast animations where you |
203 | /// expect the user to commonly interact with the child widget in its final |
204 | /// location and you want the user to benefit from "muscle memory". |
205 | final bool transformHitTests; |
206 | |
207 | /// The widget below this widget in the tree. |
208 | /// |
209 | /// {@macro flutter.widgets.ProxyWidget.child} |
210 | final Widget? child; |
211 | |
212 | @override |
213 | Widget build(BuildContext context) { |
214 | Offset offset = position.value; |
215 | if (textDirection == TextDirection.rtl) { |
216 | offset = Offset(-offset.dx, offset.dy); |
217 | } |
218 | return FractionalTranslation( |
219 | translation: offset, |
220 | transformHitTests: transformHitTests, |
221 | child: child, |
222 | ); |
223 | } |
224 | } |
225 | |
226 | /// Signature for the callback to [MatrixTransition.onTransform]. |
227 | /// |
228 | /// Computes a [Matrix4] to be used in the [MatrixTransition] transformed widget |
229 | /// from the [MatrixTransition.animation] value. |
230 | typedef TransformCallback = Matrix4 Function(double animationValue); |
231 | |
232 | /// Animates the [Matrix4] of a transformed widget. |
233 | /// |
234 | /// The [onTransform] callback computes a [Matrix4] from the animated value, it |
235 | /// is called every time the [animation] changes its value. |
236 | /// |
237 | /// {@tool dartpad} |
238 | /// The following example implements a [MatrixTransition] with a rotation around |
239 | /// the Y axis, with a 3D perspective skew. |
240 | /// |
241 | /// ** See code in examples/api/lib/widgets/transitions/matrix_transition.0.dart ** |
242 | /// {@end-tool} |
243 | /// |
244 | /// See also: |
245 | /// |
246 | /// * [ScaleTransition], which animates the scale of a widget, by providing a |
247 | /// matrix which scales along the X and Y axis. |
248 | /// * [RotationTransition], which animates the rotation of a widget, by |
249 | /// providing a matrix which rotates along the Z axis. |
250 | class MatrixTransition extends AnimatedWidget { |
251 | /// Creates a matrix transition. |
252 | /// |
253 | /// The [alignment] argument defaults to [Alignment.center]. |
254 | const MatrixTransition({ |
255 | super.key, |
256 | required Animation<double> animation, |
257 | required this.onTransform, |
258 | this.alignment = Alignment.center, |
259 | this.filterQuality, |
260 | this.child, |
261 | }) : super(listenable: animation); |
262 | |
263 | /// The callback to compute a [Matrix4] from the [animation]. It's called |
264 | /// every time [animation] changes its value. |
265 | final TransformCallback onTransform; |
266 | |
267 | /// The animation that controls the matrix of the child. |
268 | /// |
269 | /// The matrix will be computed from the animation with the [onTransform] |
270 | /// callback. |
271 | Animation<double> get animation => listenable as Animation<double>; |
272 | |
273 | /// The alignment of the origin of the coordinate system in which the |
274 | /// transform takes place, relative to the size of the box. |
275 | /// |
276 | /// For example, to set the origin of the transform to bottom middle, you can |
277 | /// use an alignment of (0.0, 1.0). |
278 | final Alignment alignment; |
279 | |
280 | /// The filter quality with which to apply the transform as a bitmap operation. |
281 | /// |
282 | /// When the animation is stopped (either in [AnimationStatus.dismissed] or |
283 | /// [AnimationStatus.completed]), the filter quality argument will be ignored. |
284 | /// |
285 | /// {@macro flutter.widgets.Transform.optional.FilterQuality} |
286 | final FilterQuality? filterQuality; |
287 | |
288 | /// The widget below this widget in the tree. |
289 | /// |
290 | /// {@macro flutter.widgets.ProxyWidget.child} |
291 | final Widget? child; |
292 | |
293 | @override |
294 | Widget build(BuildContext context) { |
295 | // The ImageFilter layer created by setting filterQuality will introduce |
296 | // a saveLayer call. This is usually worthwhile when animating the layer, |
297 | // but leaving it in the layer tree before the animation has started or after |
298 | // it has finished significantly hurts performance. |
299 | final bool useFilterQuality; |
300 | switch (animation.status) { |
301 | case AnimationStatus.dismissed: |
302 | case AnimationStatus.completed: |
303 | useFilterQuality = false; |
304 | case AnimationStatus.forward: |
305 | case AnimationStatus.reverse: |
306 | useFilterQuality = true; |
307 | } |
308 | return Transform( |
309 | transform: onTransform(animation.value), |
310 | alignment: alignment, |
311 | filterQuality: useFilterQuality ? filterQuality : null, |
312 | child: child, |
313 | ); |
314 | } |
315 | } |
316 | |
317 | /// Animates the scale of a transformed widget. |
318 | /// |
319 | /// Here's an illustration of the [ScaleTransition] widget, with it's [scale] |
320 | /// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]: |
321 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/scale_transition.mp4} |
322 | /// |
323 | /// {@tool dartpad} |
324 | /// The following code implements the [ScaleTransition] as seen in the video |
325 | /// above: |
326 | /// |
327 | /// ** See code in examples/api/lib/widgets/transitions/scale_transition.0.dart ** |
328 | /// {@end-tool} |
329 | /// |
330 | /// See also: |
331 | /// |
332 | /// * [PositionedTransition], a widget that animates its child from a start |
333 | /// position to an end position over the lifetime of the animation. |
334 | /// * [RelativePositionedTransition], a widget that transitions its child's |
335 | /// position based on the value of a rectangle relative to a bounding box. |
336 | /// * [SizeTransition], a widget that animates its own size and clips and |
337 | /// aligns its child. |
338 | class ScaleTransition extends MatrixTransition { |
339 | /// Creates a scale transition. |
340 | /// |
341 | /// The [alignment] argument defaults to [Alignment.center]. |
342 | const ScaleTransition({ |
343 | super.key, |
344 | required Animation<double> scale, |
345 | super.alignment = Alignment.center, |
346 | super.filterQuality, |
347 | super.child, |
348 | }) : super(animation: scale, onTransform: _handleScaleMatrix); |
349 | |
350 | /// The animation that controls the scale of the child. |
351 | Animation<double> get scale => animation; |
352 | |
353 | /// The callback that controls the scale of the child. |
354 | /// |
355 | /// If the current value of the animation is v, the child will be |
356 | /// painted v times its normal size. |
357 | static Matrix4 _handleScaleMatrix(double value) => Matrix4.diagonal3Values(value, value, 1.0); |
358 | } |
359 | |
360 | /// Animates the rotation of a widget. |
361 | /// |
362 | /// Here's an illustration of the [RotationTransition] widget, with it's [turns] |
363 | /// animated by a [CurvedAnimation] set to [Curves.elasticOut]: |
364 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/rotation_transition.mp4} |
365 | /// |
366 | /// {@tool dartpad} |
367 | /// The following code implements the [RotationTransition] as seen in the video |
368 | /// above: |
369 | /// |
370 | /// ** See code in examples/api/lib/widgets/transitions/rotation_transition.0.dart ** |
371 | /// {@end-tool} |
372 | /// |
373 | /// See also: |
374 | /// |
375 | /// * [ScaleTransition], a widget that animates the scale of a transformed |
376 | /// widget. |
377 | /// * [SizeTransition], a widget that animates its own size and clips and |
378 | /// aligns its child. |
379 | class RotationTransition extends MatrixTransition { |
380 | /// Creates a rotation transition. |
381 | const RotationTransition({ |
382 | super.key, |
383 | required Animation<double> turns, |
384 | super.alignment = Alignment.center, |
385 | super.filterQuality, |
386 | super.child, |
387 | }) : super(animation: turns, onTransform: _handleTurnsMatrix); |
388 | |
389 | /// The animation that controls the rotation of the child. |
390 | Animation<double> get turns => animation; |
391 | |
392 | /// The callback that controls the rotation of the child. |
393 | /// |
394 | /// If the current value of the animation is v, the child will be rotated |
395 | /// v * 2 * pi radians before being painted. |
396 | static Matrix4 _handleTurnsMatrix(double value) => Matrix4.rotationZ(value * math.pi * 2.0); |
397 | } |
398 | |
399 | /// Animates its own size and clips and aligns its child. |
400 | /// |
401 | /// [SizeTransition] acts as a [ClipRect] that animates either its width or its |
402 | /// height, depending upon the value of [axis]. The alignment of the child along |
403 | /// the [axis] is specified by the [axisAlignment]. |
404 | /// |
405 | /// Like most widgets, [SizeTransition] will conform to the constraints it is |
406 | /// given, so be sure to put it in a context where it can change size. For |
407 | /// instance, if you place it into a [Container] with a fixed size, then the |
408 | /// [SizeTransition] will not be able to change size, and will appear to do |
409 | /// nothing. |
410 | /// |
411 | /// Here's an illustration of the [SizeTransition] widget, with it's [sizeFactor] |
412 | /// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]: |
413 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/size_transition.mp4} |
414 | /// |
415 | /// {@tool dartpad} |
416 | /// This code defines a widget that uses [SizeTransition] to change the size |
417 | /// of [FlutterLogo] continually. It is built with a [Scaffold] |
418 | /// where the internal widget has space to change its size. |
419 | /// |
420 | /// ** See code in examples/api/lib/widgets/transitions/size_transition.0.dart ** |
421 | /// {@end-tool} |
422 | /// |
423 | /// See also: |
424 | /// |
425 | /// * [AnimatedCrossFade], for a widget that automatically animates between |
426 | /// the sizes of two children, fading between them. |
427 | /// * [ScaleTransition], a widget that scales the size of the child instead of |
428 | /// clipping it. |
429 | /// * [PositionedTransition], a widget that animates its child from a start |
430 | /// position to an end position over the lifetime of the animation. |
431 | /// * [RelativePositionedTransition], a widget that transitions its child's |
432 | /// position based on the value of a rectangle relative to a bounding box. |
433 | class SizeTransition extends AnimatedWidget { |
434 | /// Creates a size transition. |
435 | /// |
436 | /// The [axis] argument defaults to [Axis.vertical]. The [axisAlignment] |
437 | /// defaults to zero, which centers the child along the main axis during the |
438 | /// transition. |
439 | const SizeTransition({ |
440 | super.key, |
441 | this.axis = Axis.vertical, |
442 | required Animation<double> sizeFactor, |
443 | this.axisAlignment = 0.0, |
444 | this.fixedCrossAxisSizeFactor, |
445 | this.child, |
446 | }) : assert(fixedCrossAxisSizeFactor == null || fixedCrossAxisSizeFactor >= 0.0), |
447 | super(listenable: sizeFactor); |
448 | |
449 | /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise |
450 | /// [Axis.vertical]. |
451 | final Axis axis; |
452 | |
453 | /// The animation that controls the (clipped) size of the child. |
454 | /// |
455 | /// The width or height (depending on the [axis] value) of this widget will be |
456 | /// its intrinsic width or height multiplied by [sizeFactor]'s value at the |
457 | /// current point in the animation. |
458 | /// |
459 | /// If the value of [sizeFactor] is less than one, the child will be clipped |
460 | /// in the appropriate axis. |
461 | Animation<double> get sizeFactor => listenable as Animation<double>; |
462 | |
463 | /// Describes how to align the child along the axis that [sizeFactor] is |
464 | /// modifying. |
465 | /// |
466 | /// A value of -1.0 indicates the top when [axis] is [Axis.vertical], and the |
467 | /// start when [axis] is [Axis.horizontal]. The start is on the left when the |
468 | /// text direction in effect is [TextDirection.ltr] and on the right when it |
469 | /// is [TextDirection.rtl]. |
470 | /// |
471 | /// A value of 1.0 indicates the bottom or end, depending upon the [axis]. |
472 | /// |
473 | /// A value of 0.0 (the default) indicates the center for either [axis] value. |
474 | final double axisAlignment; |
475 | |
476 | /// The factor by which to multiply the cross axis size of the child. |
477 | /// |
478 | /// If the value of [fixedCrossAxisSizeFactor] is less than one, the child |
479 | /// will be clipped along the appropriate axis. |
480 | /// |
481 | /// If `null` (the default), the cross axis size is as large as the parent. |
482 | final double? fixedCrossAxisSizeFactor; |
483 | |
484 | /// The widget below this widget in the tree. |
485 | /// |
486 | /// {@macro flutter.widgets.ProxyWidget.child} |
487 | final Widget? child; |
488 | |
489 | @override |
490 | Widget build(BuildContext context) { |
491 | final AlignmentDirectional alignment; |
492 | if (axis == Axis.vertical) { |
493 | alignment = AlignmentDirectional(-1.0, axisAlignment); |
494 | } else { |
495 | alignment = AlignmentDirectional(axisAlignment, -1.0); |
496 | } |
497 | return ClipRect( |
498 | child: Align( |
499 | alignment: alignment, |
500 | heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : fixedCrossAxisSizeFactor, |
501 | widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : fixedCrossAxisSizeFactor, |
502 | child: child, |
503 | ), |
504 | ); |
505 | } |
506 | } |
507 | |
508 | /// Animates the opacity of a widget. |
509 | /// |
510 | /// For a widget that automatically animates between the sizes of two children, |
511 | /// fading between them, see [AnimatedCrossFade]. |
512 | /// |
513 | /// {@youtube 560 315 https://www.youtube.com/watch?v=rLwWVbv3xDQ} |
514 | /// |
515 | /// Here's an illustration of the [FadeTransition] widget, with it's [opacity] |
516 | /// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]: |
517 | /// |
518 | /// {@tool dartpad} |
519 | /// The following code implements the [FadeTransition] using |
520 | /// the Flutter logo: |
521 | /// |
522 | /// ** See code in examples/api/lib/widgets/transitions/fade_transition.0.dart ** |
523 | /// {@end-tool} |
524 | /// |
525 | /// ## Hit testing |
526 | /// |
527 | /// Setting the [opacity] to zero does not prevent hit testing from being |
528 | /// applied to the descendants of the [FadeTransition] widget. This can be |
529 | /// confusing for the user, who may not see anything, and may believe the area |
530 | /// of the interface where the [FadeTransition] is hiding a widget to be |
531 | /// non-interactive. |
532 | /// |
533 | /// With certain widgets, such as [Flow], that compute their positions only when |
534 | /// they are painted, this can actually lead to bugs (from unexpected geometry |
535 | /// to exceptions), because those widgets are not painted by the [FadeTransition] |
536 | /// widget at all when the [opacity] animation reaches zero. |
537 | /// |
538 | /// To avoid such problems, it is generally a good idea to combine this widget |
539 | /// with an [IgnorePointer] that one enables when the [opacity] animation |
540 | /// reaches zero. This prevents interactions with any children in the subtree |
541 | /// when the [child] is not visible. For performance reasons, when implementing |
542 | /// this, care should be taken not to rebuild the relevant widget (e.g. by |
543 | /// calling [State.setState]) except at the transition point. |
544 | /// |
545 | /// See also: |
546 | /// |
547 | /// * [Opacity], which does not animate changes in opacity. |
548 | /// * [AnimatedOpacity], which animates changes in opacity without taking an |
549 | /// explicit [Animation] argument. |
550 | /// * [SliverFadeTransition], the sliver version of this widget. |
551 | class FadeTransition extends SingleChildRenderObjectWidget { |
552 | /// Creates an opacity transition. |
553 | const FadeTransition({ |
554 | super.key, |
555 | required this.opacity, |
556 | this.alwaysIncludeSemantics = false, |
557 | super.child, |
558 | }); |
559 | |
560 | /// The animation that controls the opacity of the child. |
561 | /// |
562 | /// If the current value of the opacity animation is v, the child will be |
563 | /// painted with an opacity of v. For example, if v is 0.5, the child will be |
564 | /// blended 50% with its background. Similarly, if v is 0.0, the child will be |
565 | /// completely transparent. |
566 | final Animation<double> opacity; |
567 | |
568 | /// Whether the semantic information of the children is always included. |
569 | /// |
570 | /// Defaults to false. |
571 | /// |
572 | /// When true, regardless of the opacity settings the child semantic |
573 | /// information is exposed as if the widget were fully visible. This is |
574 | /// useful in cases where labels may be hidden during animations that |
575 | /// would otherwise contribute relevant semantics. |
576 | final bool alwaysIncludeSemantics; |
577 | |
578 | @override |
579 | RenderAnimatedOpacity createRenderObject(BuildContext context) { |
580 | return RenderAnimatedOpacity( |
581 | opacity: opacity, |
582 | alwaysIncludeSemantics: alwaysIncludeSemantics, |
583 | ); |
584 | } |
585 | |
586 | @override |
587 | void updateRenderObject(BuildContext context, RenderAnimatedOpacity renderObject) { |
588 | renderObject |
589 | ..opacity = opacity |
590 | ..alwaysIncludeSemantics = alwaysIncludeSemantics; |
591 | } |
592 | |
593 | @override |
594 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
595 | super.debugFillProperties(properties); |
596 | properties.add(DiagnosticsProperty<Animation<double>>('opacity' , opacity)); |
597 | properties.add(FlagProperty('alwaysIncludeSemantics' , value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics' )); |
598 | } |
599 | } |
600 | |
601 | /// Animates the opacity of a sliver widget. |
602 | /// |
603 | /// {@tool dartpad} |
604 | /// Creates a [CustomScrollView] with a [SliverFixedExtentList] that uses a |
605 | /// [SliverFadeTransition] to fade the list in and out. |
606 | /// |
607 | /// ** See code in examples/api/lib/widgets/transitions/sliver_fade_transition.0.dart ** |
608 | /// {@end-tool} |
609 | /// |
610 | /// Here's an illustration of the [FadeTransition] widget, the [RenderBox] |
611 | /// equivalent widget, with it's [opacity] animated by a [CurvedAnimation] set |
612 | /// to [Curves.fastOutSlowIn]: |
613 | /// |
614 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/fade_transition.mp4} |
615 | /// |
616 | /// ## Hit testing |
617 | /// |
618 | /// Setting the [opacity] to zero does not prevent hit testing from being |
619 | /// applied to the descendants of the [SliverFadeTransition] widget. This can be |
620 | /// confusing for the user, who may not see anything, and may believe the area |
621 | /// of the interface where the [SliverFadeTransition] is hiding a widget to be |
622 | /// non-interactive. |
623 | /// |
624 | /// With certain widgets, such as [Flow], that compute their positions only when |
625 | /// they are painted, this can actually lead to bugs (from unexpected geometry |
626 | /// to exceptions), because those widgets are not painted by the |
627 | /// [SliverFadeTransition] widget at all when the [opacity] animation reaches |
628 | /// zero. |
629 | /// |
630 | /// To avoid such problems, it is generally a good idea to combine this widget |
631 | /// with a [SliverIgnorePointer] that one enables when the [opacity] animation |
632 | /// reaches zero. This prevents interactions with any children in the subtree |
633 | /// when the [sliver] is not visible. For performance reasons, when implementing |
634 | /// this, care should be taken not to rebuild the relevant widget (e.g. by |
635 | /// calling [State.setState]) except at the transition point. |
636 | /// |
637 | /// See also: |
638 | /// |
639 | /// * [SliverOpacity], which does not animate changes in opacity. |
640 | /// * [FadeTransition], the box version of this widget. |
641 | class SliverFadeTransition extends SingleChildRenderObjectWidget { |
642 | /// Creates an opacity transition. |
643 | const SliverFadeTransition({ |
644 | super.key, |
645 | required this.opacity, |
646 | this.alwaysIncludeSemantics = false, |
647 | Widget? sliver, |
648 | }) : super(child: sliver); |
649 | |
650 | /// The animation that controls the opacity of the sliver child. |
651 | /// |
652 | /// If the current value of the opacity animation is v, the child will be |
653 | /// painted with an opacity of v. For example, if v is 0.5, the child will be |
654 | /// blended 50% with its background. Similarly, if v is 0.0, the child will be |
655 | /// completely transparent. |
656 | final Animation<double> opacity; |
657 | |
658 | /// Whether the semantic information of the sliver child is always included. |
659 | /// |
660 | /// Defaults to false. |
661 | /// |
662 | /// When true, regardless of the opacity settings the sliver child's semantic |
663 | /// information is exposed as if the widget were fully visible. This is |
664 | /// useful in cases where labels may be hidden during animations that |
665 | /// would otherwise contribute relevant semantics. |
666 | final bool alwaysIncludeSemantics; |
667 | |
668 | @override |
669 | RenderSliverAnimatedOpacity createRenderObject(BuildContext context) { |
670 | return RenderSliverAnimatedOpacity( |
671 | opacity: opacity, |
672 | alwaysIncludeSemantics: alwaysIncludeSemantics, |
673 | ); |
674 | } |
675 | |
676 | @override |
677 | void updateRenderObject(BuildContext context, RenderSliverAnimatedOpacity renderObject) { |
678 | renderObject |
679 | ..opacity = opacity |
680 | ..alwaysIncludeSemantics = alwaysIncludeSemantics; |
681 | } |
682 | |
683 | @override |
684 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
685 | super.debugFillProperties(properties); |
686 | properties.add(DiagnosticsProperty<Animation<double>>('opacity' , opacity)); |
687 | properties.add(FlagProperty('alwaysIncludeSemantics' , value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics' )); |
688 | } |
689 | } |
690 | |
691 | /// An interpolation between two relative rects. |
692 | /// |
693 | /// This class specializes the interpolation of [Tween<RelativeRect>] to |
694 | /// use [RelativeRect.lerp]. |
695 | /// |
696 | /// See [Tween] for a discussion on how to use interpolation objects. |
697 | class RelativeRectTween extends Tween<RelativeRect> { |
698 | /// Creates a [RelativeRect] tween. |
699 | /// |
700 | /// The [begin] and [end] properties may be null; the null value |
701 | /// is treated as [RelativeRect.fill]. |
702 | RelativeRectTween({ super.begin, super.end }); |
703 | |
704 | /// Returns the value this variable has at the given animation clock value. |
705 | @override |
706 | RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t)!; |
707 | } |
708 | |
709 | /// Animated version of [Positioned] which takes a specific |
710 | /// [Animation<RelativeRect>] to transition the child's position from a start |
711 | /// position to an end position over the lifetime of the animation. |
712 | /// |
713 | /// Only works if it's the child of a [Stack]. |
714 | /// |
715 | /// Here's an illustration of the [PositionedTransition] widget, with it's [rect] |
716 | /// animated by a [CurvedAnimation] set to [Curves.elasticInOut]: |
717 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/positioned_transition.mp4} |
718 | /// |
719 | /// {@tool dartpad} |
720 | /// The following code implements the [PositionedTransition] as seen in the video |
721 | /// above: |
722 | /// |
723 | /// ** See code in examples/api/lib/widgets/transitions/positioned_transition.0.dart ** |
724 | /// {@end-tool} |
725 | /// |
726 | /// See also: |
727 | /// |
728 | /// * [AnimatedPositioned], which transitions a child's position without |
729 | /// taking an explicit [Animation] argument. |
730 | /// * [RelativePositionedTransition], a widget that transitions its child's |
731 | /// position based on the value of a rectangle relative to a bounding box. |
732 | /// * [SlideTransition], a widget that animates the position of a widget |
733 | /// relative to its normal position. |
734 | /// * [AlignTransition], an animated version of an [Align] that animates its |
735 | /// [Align.alignment] property. |
736 | /// * [ScaleTransition], a widget that animates the scale of a transformed |
737 | /// widget. |
738 | /// * [SizeTransition], a widget that animates its own size and clips and |
739 | /// aligns its child. |
740 | class PositionedTransition extends AnimatedWidget { |
741 | /// Creates a transition for [Positioned]. |
742 | const PositionedTransition({ |
743 | super.key, |
744 | required Animation<RelativeRect> rect, |
745 | required this.child, |
746 | }) : super(listenable: rect); |
747 | |
748 | /// The animation that controls the child's size and position. |
749 | Animation<RelativeRect> get rect => listenable as Animation<RelativeRect>; |
750 | |
751 | /// The widget below this widget in the tree. |
752 | /// |
753 | /// {@macro flutter.widgets.ProxyWidget.child} |
754 | final Widget child; |
755 | |
756 | @override |
757 | Widget build(BuildContext context) { |
758 | return Positioned.fromRelativeRect( |
759 | rect: rect.value, |
760 | child: child, |
761 | ); |
762 | } |
763 | } |
764 | |
765 | /// Animated version of [Positioned] which transitions the child's position |
766 | /// based on the value of [rect] relative to a bounding box with the |
767 | /// specified [size]. |
768 | /// |
769 | /// Only works if it's the child of a [Stack]. |
770 | /// |
771 | /// Here's an illustration of the [RelativePositionedTransition] widget, with it's [rect] |
772 | /// animated by a [CurvedAnimation] set to [Curves.elasticInOut]: |
773 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/relative_positioned_transition.mp4} |
774 | /// |
775 | /// {@tool dartpad} |
776 | /// The following code implements the [RelativePositionedTransition] as seen in the video |
777 | /// above: |
778 | /// |
779 | /// ** See code in examples/api/lib/widgets/transitions/relative_positioned_transition.0.dart ** |
780 | /// {@end-tool} |
781 | /// |
782 | /// See also: |
783 | /// |
784 | /// * [PositionedTransition], a widget that animates its child from a start |
785 | /// position to an end position over the lifetime of the animation. |
786 | /// * [AlignTransition], an animated version of an [Align] that animates its |
787 | /// [Align.alignment] property. |
788 | /// * [ScaleTransition], a widget that animates the scale of a transformed |
789 | /// widget. |
790 | /// * [SizeTransition], a widget that animates its own size and clips and |
791 | /// aligns its child. |
792 | /// * [SlideTransition], a widget that animates the position of a widget |
793 | /// relative to its normal position. |
794 | class RelativePositionedTransition extends AnimatedWidget { |
795 | /// Create an animated version of [Positioned]. |
796 | /// |
797 | /// Each frame, the [Positioned] widget will be configured to represent the |
798 | /// current value of the [rect] argument assuming that the stack has the given |
799 | /// [size]. |
800 | const RelativePositionedTransition({ |
801 | super.key, |
802 | required Animation<Rect?> rect, |
803 | required this.size, |
804 | required this.child, |
805 | }) : super(listenable: rect); |
806 | |
807 | /// The animation that controls the child's size and position. |
808 | /// |
809 | /// If the animation returns a null [Rect], the rect is assumed to be [Rect.zero]. |
810 | /// |
811 | /// See also: |
812 | /// |
813 | /// * [size], which gets the size of the box that the [Positioned] widget's |
814 | /// offsets are relative to. |
815 | Animation<Rect?> get rect => listenable as Animation<Rect?>; |
816 | |
817 | /// The [Positioned] widget's offsets are relative to a box of this |
818 | /// size whose origin is 0,0. |
819 | final Size size; |
820 | |
821 | /// The widget below this widget in the tree. |
822 | /// |
823 | /// {@macro flutter.widgets.ProxyWidget.child} |
824 | final Widget child; |
825 | |
826 | @override |
827 | Widget build(BuildContext context) { |
828 | final RelativeRect offsets = RelativeRect.fromSize(rect.value ?? Rect.zero, size); |
829 | return Positioned( |
830 | top: offsets.top, |
831 | right: offsets.right, |
832 | bottom: offsets.bottom, |
833 | left: offsets.left, |
834 | child: child, |
835 | ); |
836 | } |
837 | } |
838 | |
839 | /// Animated version of a [DecoratedBox] that animates the different properties |
840 | /// of its [Decoration]. |
841 | /// |
842 | /// Here's an illustration of the [DecoratedBoxTransition] widget, with it's |
843 | /// [decoration] animated by a [CurvedAnimation] set to [Curves.decelerate]: |
844 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/decorated_box_transition.mp4} |
845 | /// |
846 | /// {@tool dartpad} |
847 | /// The following code implements the [DecoratedBoxTransition] as seen in the video |
848 | /// above: |
849 | /// |
850 | /// ** See code in examples/api/lib/widgets/transitions/decorated_box_transition.0.dart ** |
851 | /// {@end-tool} |
852 | /// |
853 | /// See also: |
854 | /// |
855 | /// * [DecoratedBox], which also draws a [Decoration] but is not animated. |
856 | /// * [AnimatedContainer], a more full-featured container that also animates on |
857 | /// decoration using an internal animation. |
858 | class DecoratedBoxTransition extends AnimatedWidget { |
859 | /// Creates an animated [DecoratedBox] whose [Decoration] animation updates |
860 | /// the widget. |
861 | /// |
862 | /// See also: |
863 | /// |
864 | /// * [DecoratedBox.new] |
865 | const DecoratedBoxTransition({ |
866 | super.key, |
867 | required this.decoration, |
868 | this.position = DecorationPosition.background, |
869 | required this.child, |
870 | }) : super(listenable: decoration); |
871 | |
872 | /// Animation of the decoration to paint. |
873 | /// |
874 | /// Can be created using a [DecorationTween] interpolating typically between |
875 | /// two [BoxDecoration]. |
876 | final Animation<Decoration> decoration; |
877 | |
878 | /// Whether to paint the box decoration behind or in front of the child. |
879 | final DecorationPosition position; |
880 | |
881 | /// The widget below this widget in the tree. |
882 | /// |
883 | /// {@macro flutter.widgets.ProxyWidget.child} |
884 | final Widget child; |
885 | |
886 | @override |
887 | Widget build(BuildContext context) { |
888 | return DecoratedBox( |
889 | decoration: decoration.value, |
890 | position: position, |
891 | child: child, |
892 | ); |
893 | } |
894 | } |
895 | |
896 | /// Animated version of an [Align] that animates its [Align.alignment] property. |
897 | /// |
898 | /// Here's an illustration of the [DecoratedBoxTransition] widget, with it's |
899 | /// [DecoratedBoxTransition.decoration] animated by a [CurvedAnimation] set to |
900 | /// [Curves.decelerate]: |
901 | /// |
902 | /// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/align_transition.mp4} |
903 | /// |
904 | /// {@tool dartpad} |
905 | /// The following code implements the [AlignTransition] as seen in the video |
906 | /// above: |
907 | /// |
908 | /// ** See code in examples/api/lib/widgets/transitions/align_transition.0.dart ** |
909 | /// {@end-tool} |
910 | /// |
911 | /// See also: |
912 | /// |
913 | /// * [AnimatedAlign], which animates changes to the [alignment] without |
914 | /// taking an explicit [Animation] argument. |
915 | /// * [PositionedTransition], a widget that animates its child from a start |
916 | /// position to an end position over the lifetime of the animation. |
917 | /// * [RelativePositionedTransition], a widget that transitions its child's |
918 | /// position based on the value of a rectangle relative to a bounding box. |
919 | /// * [SizeTransition], a widget that animates its own size and clips and |
920 | /// aligns its child. |
921 | /// * [SlideTransition], a widget that animates the position of a widget |
922 | /// relative to its normal position. |
923 | class AlignTransition extends AnimatedWidget { |
924 | /// Creates an animated [Align] whose [AlignmentGeometry] animation updates |
925 | /// the widget. |
926 | /// |
927 | /// See also: |
928 | /// |
929 | /// * [Align.new]. |
930 | const AlignTransition({ |
931 | super.key, |
932 | required Animation<AlignmentGeometry> alignment, |
933 | required this.child, |
934 | this.widthFactor, |
935 | this.heightFactor, |
936 | }) : super(listenable: alignment); |
937 | |
938 | /// The animation that controls the child's alignment. |
939 | Animation<AlignmentGeometry> get alignment => listenable as Animation<AlignmentGeometry>; |
940 | |
941 | /// If non-null, the child's width factor, see [Align.widthFactor]. |
942 | final double? widthFactor; |
943 | |
944 | /// If non-null, the child's height factor, see [Align.heightFactor]. |
945 | final double? heightFactor; |
946 | |
947 | /// The widget below this widget in the tree. |
948 | /// |
949 | /// {@macro flutter.widgets.ProxyWidget.child} |
950 | final Widget child; |
951 | |
952 | @override |
953 | Widget build(BuildContext context) { |
954 | return Align( |
955 | alignment: alignment.value, |
956 | widthFactor: widthFactor, |
957 | heightFactor: heightFactor, |
958 | child: child, |
959 | ); |
960 | } |
961 | } |
962 | |
963 | /// Animated version of a [DefaultTextStyle] that animates the different properties |
964 | /// of its [TextStyle]. |
965 | /// |
966 | /// {@tool dartpad} |
967 | /// The following code implements the [DefaultTextStyleTransition] that shows |
968 | /// a transition between thick blue font and thin red font. |
969 | /// |
970 | /// ** See code in examples/api/lib/widgets/transitions/default_text_style_transition.0.dart ** |
971 | /// {@end-tool} |
972 | /// |
973 | /// See also: |
974 | /// |
975 | /// * [AnimatedDefaultTextStyle], which animates changes in text style without |
976 | /// taking an explicit [Animation] argument. |
977 | /// * [DefaultTextStyle], which also defines a [TextStyle] for its descendants |
978 | /// but is not animated. |
979 | class DefaultTextStyleTransition extends AnimatedWidget { |
980 | /// Creates an animated [DefaultTextStyle] whose [TextStyle] animation updates |
981 | /// the widget. |
982 | const DefaultTextStyleTransition({ |
983 | super.key, |
984 | required Animation<TextStyle> style, |
985 | required this.child, |
986 | this.textAlign, |
987 | this.softWrap = true, |
988 | this.overflow = TextOverflow.clip, |
989 | this.maxLines, |
990 | }) : super(listenable: style); |
991 | |
992 | /// The animation that controls the descendants' text style. |
993 | Animation<TextStyle> get style => listenable as Animation<TextStyle>; |
994 | |
995 | /// How the text should be aligned horizontally. |
996 | final TextAlign? textAlign; |
997 | |
998 | /// Whether the text should break at soft line breaks. |
999 | /// |
1000 | /// See [DefaultTextStyle.softWrap] for more details. |
1001 | final bool softWrap; |
1002 | |
1003 | /// How visual overflow should be handled. |
1004 | /// |
1005 | final TextOverflow overflow; |
1006 | |
1007 | /// An optional maximum number of lines for the text to span, wrapping if necessary. |
1008 | /// |
1009 | /// See [DefaultTextStyle.maxLines] for more details. |
1010 | final int? maxLines; |
1011 | |
1012 | /// The widget below this widget in the tree. |
1013 | /// |
1014 | /// {@macro flutter.widgets.ProxyWidget.child} |
1015 | final Widget child; |
1016 | |
1017 | @override |
1018 | Widget build(BuildContext context) { |
1019 | return DefaultTextStyle( |
1020 | style: style.value, |
1021 | textAlign: textAlign, |
1022 | softWrap: softWrap, |
1023 | overflow: overflow, |
1024 | maxLines: maxLines, |
1025 | child: child, |
1026 | ); |
1027 | } |
1028 | } |
1029 | |
1030 | /// A general-purpose widget for building a widget subtree when a [Listenable] |
1031 | /// changes. |
1032 | /// |
1033 | /// [ListenableBuilder] is useful for more complex widgets that wish to listen |
1034 | /// to changes in other objects as part of a larger build function. To use |
1035 | /// [ListenableBuilder], construct the widget and pass it a [builder] |
1036 | /// function. |
1037 | /// |
1038 | /// Any subtype of [Listenable] (such as a [ChangeNotifier], [ValueNotifier], or |
1039 | /// [Animation]) can be used with a [ListenableBuilder] to rebuild only certain |
1040 | /// parts of a widget when the [Listenable] notifies its listeners. Although |
1041 | /// they have identical implementations, if an [Animation] is being listened to, |
1042 | /// consider using an [AnimatedBuilder] instead for better readability. |
1043 | /// |
1044 | /// {@tool dartpad} |
1045 | /// The following example uses a subclass of [ChangeNotifier] to hold the |
1046 | /// application model's state, in this case, a counter. A [ListenableBuilder] is |
1047 | /// then used to update the rendering (a [Text] widget) whenever the model changes. |
1048 | /// |
1049 | /// ** See code in examples/api/lib/widgets/transitions/listenable_builder.2.dart ** |
1050 | /// {@end-tool} |
1051 | /// |
1052 | /// {@tool dartpad} |
1053 | /// This version is identical, but using a [ValueNotifier] instead of a |
1054 | /// dedicated subclass of [ChangeNotifier]. This works well when there is only a |
1055 | /// single immutable value to be tracked. |
1056 | /// |
1057 | /// ** See code in examples/api/lib/widgets/transitions/listenable_builder.1.dart ** |
1058 | /// {@end-tool} |
1059 | /// |
1060 | /// ## Performance optimizations |
1061 | /// |
1062 | /// {@template flutter.widgets.transitions.ListenableBuilder.optimizations} |
1063 | /// If the [builder] function contains a subtree that does not depend on the |
1064 | /// [listenable], it is more efficient to build that subtree once instead |
1065 | /// of rebuilding it on every change of the [listenable]. |
1066 | /// |
1067 | /// Performance is therefore improved by specifying any widgets that don't need |
1068 | /// to change using the prebuilt [child] attribute. The [ListenableBuilder] |
1069 | /// passes this [child] back to the [builder] callback so that it can be |
1070 | /// incorporated into the build. |
1071 | /// |
1072 | /// Using this pre-built [child] is entirely optional, but can improve |
1073 | /// performance significantly in some cases and is therefore a good practice. |
1074 | /// {@endtemplate} |
1075 | /// |
1076 | /// {@tool dartpad} |
1077 | /// This example shows how a [ListenableBuilder] can be used to listen to a |
1078 | /// [FocusNode] (which is also a [ChangeNotifier]) to see when a subtree has |
1079 | /// focus, and modify a decoration when its focus state changes. Only the |
1080 | /// [Container] is rebuilt when the [FocusNode] changes; the rest of the tree |
1081 | /// (notably the [Focus] widget) remain unchanged from frame to frame. |
1082 | /// |
1083 | /// ** See code in examples/api/lib/widgets/transitions/listenable_builder.0.dart ** |
1084 | /// {@end-tool} |
1085 | /// |
1086 | /// See also: |
1087 | /// |
1088 | /// * [AnimatedBuilder], which has the same functionality, but is named more |
1089 | /// appropriately for a builder triggered by [Animation]s. |
1090 | /// * [ValueListenableBuilder], which is specialized for [ValueNotifier]s and |
1091 | /// reports the new value in its builder callback. |
1092 | class ListenableBuilder extends AnimatedWidget { |
1093 | /// Creates a builder that responds to changes in [listenable]. |
1094 | const ListenableBuilder({ |
1095 | super.key, |
1096 | required super.listenable, |
1097 | required this.builder, |
1098 | this.child, |
1099 | }); |
1100 | |
1101 | /// The [Listenable] supplied to the constructor. |
1102 | /// |
1103 | /// {@tool dartpad} |
1104 | /// In this example, the [listenable] is a [ChangeNotifier] subclass that |
1105 | /// encapsulates a list. The [ListenableBuilder] is rebuilt each time an item |
1106 | /// is added to the list. |
1107 | /// |
1108 | /// ** See code in examples/api/lib/widgets/transitions/listenable_builder.3.dart ** |
1109 | /// {@end-tool} |
1110 | /// |
1111 | /// See also: |
1112 | /// |
1113 | /// * [AnimatedBuilder], a widget with identical functionality commonly |
1114 | /// used with [Animation] [Listenable]s for better readability. |
1115 | // |
1116 | // Overridden getter to replace with documentation tailored to |
1117 | // ListenableBuilder. |
1118 | @override |
1119 | Listenable get listenable => super.listenable; |
1120 | |
1121 | /// Called every time the [listenable] notifies about a change. |
1122 | /// |
1123 | /// The child given to the builder should typically be part of the returned |
1124 | /// widget tree. |
1125 | final TransitionBuilder builder; |
1126 | |
1127 | /// The child widget to pass to the [builder]. |
1128 | /// |
1129 | /// {@macro flutter.widgets.transitions.ListenableBuilder.optimizations} |
1130 | final Widget? child; |
1131 | |
1132 | @override |
1133 | Widget build(BuildContext context) => builder(context, child); |
1134 | } |
1135 | |
1136 | /// A general-purpose widget for building animations. |
1137 | /// |
1138 | /// [AnimatedBuilder] is useful for more complex widgets that wish to include |
1139 | /// an animation as part of a larger build function. To use [AnimatedBuilder], |
1140 | /// construct the widget and pass it a builder function. |
1141 | /// |
1142 | /// For simple cases without additional state, consider using |
1143 | /// [AnimatedWidget]. |
1144 | /// |
1145 | /// {@youtube 560 315 https://www.youtube.com/watch?v=N-RiyZlv8v8} |
1146 | /// |
1147 | /// Despite the name, [AnimatedBuilder] is not limited to [Animation]s, any |
1148 | /// subtype of [Listenable] (such as [ChangeNotifier] or [ValueNotifier]) can be |
1149 | /// used to trigger rebuilds. Although they have identical implementations, if |
1150 | /// an [Animation] is not being listened to, consider using a |
1151 | /// [ListenableBuilder] for better readability. |
1152 | /// |
1153 | /// ## Performance optimizations |
1154 | /// |
1155 | /// {@template flutter.widgets.transitions.AnimatedBuilder.optimizations} |
1156 | /// If the [builder] function contains a subtree that does not depend on the |
1157 | /// animation passed to the constructor, it's more efficient to build that |
1158 | /// subtree once instead of rebuilding it on every animation tick. |
1159 | /// |
1160 | /// If a pre-built subtree is passed as the [child] parameter, the |
1161 | /// [AnimatedBuilder] will pass it back to the [builder] function so that it can |
1162 | /// be incorporated into the build. |
1163 | /// |
1164 | /// Using this pre-built child is entirely optional, but can improve |
1165 | /// performance significantly in some cases and is therefore a good practice. |
1166 | /// {@endtemplate} |
1167 | /// |
1168 | /// {@tool dartpad} |
1169 | /// This code defines a widget that spins a green square continually. It is |
1170 | /// built with an [AnimatedBuilder] and makes use of the [child] feature to |
1171 | /// avoid having to rebuild the [Container] each time. |
1172 | /// |
1173 | /// ** See code in examples/api/lib/widgets/transitions/animated_builder.0.dart ** |
1174 | /// {@end-tool} |
1175 | /// |
1176 | /// See also: |
1177 | /// |
1178 | /// * [ListenableBuilder], a widget with similar functionality, but named |
1179 | /// more appropriately for a builder triggered on changes in [Listenable]s |
1180 | /// that aren't [Animation]s. |
1181 | /// * [TweenAnimationBuilder], which animates a property to a target value |
1182 | /// without requiring manual management of an [AnimationController]. |
1183 | class AnimatedBuilder extends ListenableBuilder { |
1184 | /// Creates an animated builder. |
1185 | /// |
1186 | /// The [animation] and [builder] arguments are required. |
1187 | const AnimatedBuilder({ |
1188 | super.key, |
1189 | required Listenable animation, |
1190 | required super.builder, |
1191 | super.child, |
1192 | }) : super(listenable: animation); |
1193 | |
1194 | /// The [Listenable] supplied to the constructor (typically an [Animation]). |
1195 | /// |
1196 | /// Also accessible through the [listenable] getter. |
1197 | /// |
1198 | /// See also: |
1199 | /// |
1200 | /// * [ListenableBuilder], a widget with similar functionality commonly used |
1201 | /// with [Listenable]s (such as [ChangeNotifier]) for better readability |
1202 | /// when the [animation] isn't an [Animation]. |
1203 | Listenable get animation => super.listenable; |
1204 | |
1205 | /// The [Listenable] supplied to the constructor (typically an [Animation]). |
1206 | /// |
1207 | /// Also accessible through the [animation] getter. |
1208 | /// |
1209 | /// See also: |
1210 | /// |
1211 | /// * [ListenableBuilder], a widget with identical functionality commonly |
1212 | /// used with non-animation [Listenable]s for readability. |
1213 | // |
1214 | // Overridden getter to replace with documentation tailored to |
1215 | // ListenableBuilder. |
1216 | @override |
1217 | Listenable get listenable => super.listenable; |
1218 | |
1219 | /// Called every time the [animation] notifies about a change. |
1220 | /// |
1221 | /// The child given to the builder should typically be part of the returned |
1222 | /// widget tree. |
1223 | // |
1224 | // Overridden getter to replace with documentation tailored to |
1225 | // AnimatedBuilder. |
1226 | @override |
1227 | TransitionBuilder get builder => super.builder; |
1228 | } |
1229 | |