transitions.dart 15.7 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Hixie's avatar
Hixie committed
5 6
import 'dart:math' as math;

7
import 'package:flutter/foundation.dart';
Hixie's avatar
Hixie committed
8
import 'package:vector_math/vector_math_64.dart' show Matrix4;
9

10
import 'basic.dart';
11
import 'container.dart';
12 13
import 'framework.dart';

14
export 'package:flutter/rendering.dart' show RelativeRect;
15

16
/// A widget that rebuilds when the given [Listenable] changes value.
17
///
18 19 20 21 22 23 24
/// [AnimatedWidget] is most common used with [Animation] objects, which are
/// [Listenable], but it can be used with any [Listenable], including
/// [ChangeNotifier] and [ValueNotifier].
///
/// [AnimatedWidget] is most useful for widgets widgets that are otherwise
/// stateless. To use [AnimatedWidget], simply subclass it and implement the
/// build function.
25 26 27
///
/// For more complex case involving additional state, consider using
/// [AnimatedBuilder].
28 29 30 31 32 33 34 35
///
/// See also:
///
///  * [AnimatedBuilder], which is useful for more complex use cases.
///  * [Animation], which is a [Listenable] object that can be used for
///    [listenable].
///  * [ChangeNotifier], which is another [Listenable] object that can be used
///    for [listenable].
36
abstract class AnimatedWidget extends StatefulWidget {
37
  /// Creates a widget that rebuilds when the given listenable changes.
38
  ///
39
  /// The [listenable] argument is required.
40
  const AnimatedWidget({
41
    Key key,
42
    @required this.listenable
43 44
  }) : assert(listenable != null),
       super(key: key);
45

46 47 48 49
  /// The [Listenable] to which this widget is listening.
  ///
  /// Commonly an [Animation] or a [ChangeNotifier].
  final Listenable listenable;
50

51 52
  /// Override this method to build widgets that depend on the state of the
  /// listenable (e.g., the current value of the animation).
53
  @protected
54 55
  Widget build(BuildContext context);

56
  /// Subclasses typically do not override this method.
57
  @override
58
  _AnimatedState createState() => new _AnimatedState();
59

60
  @override
61 62
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
63
    description.add('animation: $listenable');
64 65 66
  }
}

67
class _AnimatedState extends State<AnimatedWidget> {
68
  @override
69 70
  void initState() {
    super.initState();
71
    widget.listenable.addListener(_handleChange);
72 73
  }

74
  @override
75
  void didUpdateWidget(AnimatedWidget oldWidget) {
76
    super.didUpdateWidget(oldWidget);
77 78 79
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
80 81 82
    }
  }

83
  @override
84
  void dispose() {
85
    widget.listenable.removeListener(_handleChange);
86 87 88
    super.dispose();
  }

89
  void _handleChange() {
90
    setState(() {
91
      // The listenable's state is our build state, and it changed already.
92 93 94
    });
  }

95
  @override
96
  Widget build(BuildContext context) => widget.build(context);
97 98
}

99
/// Animates the position of a widget relative to its normal position.
100
class SlideTransition extends AnimatedWidget {
101 102
  /// Creates a fractional translation transition.
  ///
103
  /// The [position] argument must not be null.
104
  const SlideTransition({
105
    Key key,
106
    @required Animation<FractionalOffset> position,
107
    this.transformHitTests: true,
108
    this.child,
109
  }) : super(key: key, listenable: position);
110

111 112 113 114
  /// The animation that controls the position of the child.
  ///
  /// If the current value of the position animation is (dx, dy), the child will
  /// be translated horizontally by width * dx and vertically by height * dy.
115
  Animation<FractionalOffset> get position => listenable;
116 117 118 119 120 121 122

  /// Whether hit testing should be affected by the slide animation.
  ///
  /// If false, hit testing will proceed as if the child was not translated at
  /// all. Setting this value to false is useful for fast animations where you
  /// expect the user to commonly interact with the child widget in its final
  /// location and you want the user to benefit from "muscle memory".
123
  final bool transformHitTests;
124

125
  /// The widget below this widget in the tree.
126
  final Widget child;
127

128
  @override
129 130 131 132
  Widget build(BuildContext context) {
    return new FractionalTranslation(
      translation: position.value,
      transformHitTests: transformHitTests,
133
      child: child,
134
    );
135 136 137
  }
}

Hans Muller's avatar
Hans Muller committed
138
/// Animates the scale of transformed widget.
139
class ScaleTransition extends AnimatedWidget {
140 141
  /// Creates a scale transition.
  ///
142 143
  /// The [scale] argument must not be null. The [alignment] argument defaults
  /// to [FractionalOffset.center].
144
  const ScaleTransition({
145
    Key key,
146
    @required Animation<double> scale,
147
    this.alignment: FractionalOffset.center,
148
    this.child,
149
  }) : super(key: key, listenable: scale);
150

151 152 153 154
  /// The animation that controls the scale of the child.
  ///
  /// If the current value of the scale animation is v, the child will be
  /// painted v times its normal size.
155
  Animation<double> get scale => listenable;
156 157 158 159 160 161

  /// The alignment of the origin of the coordainte system in which the scale
  /// takes place, relative to the size of the box.
  ///
  /// For example, to set the origin of the scale to bottom middle, you can use
  /// an alignment of (0.5, 1.0).
162
  final FractionalOffset alignment;
163

164
  /// The widget below this widget in the tree.
165
  final Widget child;
166

167
  @override
168
  Widget build(BuildContext context) {
169
    final double scaleValue = scale.value;
170
    final Matrix4 transform = new Matrix4.identity()
171
      ..scale(scaleValue, scaleValue, 1.0);
172 173 174
    return new Transform(
      transform: transform,
      alignment: alignment,
175
      child: child,
176 177 178 179
    );
  }
}

180
/// Animates the rotation of a widget.
181
class RotationTransition extends AnimatedWidget {
182 183
  /// Creates a rotation transition.
  ///
184
  /// The [turns] argument must not be null.
185
  const RotationTransition({
Hixie's avatar
Hixie committed
186
    Key key,
187
    @required Animation<double> turns,
188
    this.child,
189
  }) : super(key: key, listenable: turns);
Hixie's avatar
Hixie committed
190

191 192 193 194
  /// The animation that controls the rotation of the child.
  ///
  /// If the current value of the turns animation is v, the child will be
  /// rotated v * 2 * pi radians before being painted.
195
  Animation<double> get turns => listenable;
196

197
  /// The widget below this widget in the tree.
198
  final Widget child;
Hixie's avatar
Hixie committed
199

200
  @override
201
  Widget build(BuildContext context) {
202 203
    final double turnsValue = turns.value;
    final Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.PI * 2.0);
Hixie's avatar
Hixie committed
204 205
    return new Transform(
      transform: transform,
206
      alignment: FractionalOffset.center,
207
      child: child,
Hixie's avatar
Hixie committed
208 209 210 211
    );
  }
}

212
/// Animates its own size and clips and aligns the child.
Ian Hickson's avatar
Ian Hickson committed
213 214 215
///
/// For a widget that automatically animates between the sizes of two children,
/// fading between them, see [AnimatedCrossFade].
216
class SizeTransition extends AnimatedWidget {
217 218
  /// Creates a size transition.
  ///
219 220
  /// The [sizeFactor] argument must not be null. The [axis] argument defaults
  /// to [Axis.vertical]. The [axisAlignment] defaults to 0.5, which centers the
221
  /// child along the main axis during the transition.
222
  const SizeTransition({
Hans Muller's avatar
Hans Muller committed
223 224
    Key key,
    this.axis: Axis.vertical,
225
    @required Animation<double> sizeFactor,
226
    this.axisAlignment: 0.5,
227
    this.child,
228 229
  }) : assert(axis != null),
       super(key: key, listenable: sizeFactor);
Hans Muller's avatar
Hans Muller committed
230 231 232 233 234 235 236

  /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise [Axis.vertical].
  final Axis axis;

  /// The animation that controls the (clipped) size of the child. If the current value
  /// of sizeFactor is v then the width or height of the widget will be its intrinsic
  /// width or height multiplied by v.
237
  Animation<double> get sizeFactor => listenable;
Hans Muller's avatar
Hans Muller committed
238

239 240
  /// How to align the child along the axis that sizeFactor is modifying.
  final double axisAlignment;
Hans Muller's avatar
Hans Muller committed
241

242
  /// The widget below this widget in the tree.
Hans Muller's avatar
Hans Muller committed
243 244
  final Widget child;

245
  @override
Hans Muller's avatar
Hans Muller committed
246
  Widget build(BuildContext context) {
247 248 249 250 251
    FractionalOffset alignment;
    if (axis == Axis.vertical)
      alignment = new FractionalOffset(0.0, axisAlignment);
    else
      alignment = new FractionalOffset(axisAlignment, 0.0);
Hans Muller's avatar
Hans Muller committed
252 253 254 255 256
    return new ClipRect(
      child: new Align(
        alignment: alignment,
        heightFactor: axis == Axis.vertical ? sizeFactor.value : null,
        widthFactor: axis == Axis.horizontal ? sizeFactor.value : null,
257
        child: child,
Hans Muller's avatar
Hans Muller committed
258 259 260 261 262
      )
    );
  }
}

263
/// Animates the opacity of a widget.
Ian Hickson's avatar
Ian Hickson committed
264 265 266
///
/// For a widget that automatically animates between the sizes of two children,
/// fading between them, see [AnimatedCrossFade].
267
class FadeTransition extends AnimatedWidget {
268 269
  /// Creates an opacity transition.
  ///
270
  /// The [opacity] argument must not be null.
271
  const FadeTransition({
272
    Key key,
273
    @required Animation<double> opacity,
274
    this.child,
275
  }) : super(key: key, listenable: opacity);
276

277 278 279 280 281 282
  /// The animation that controls the opacity of the child.
  ///
  /// If the current value of the opacity animation is v, the child will be
  /// painted with an opacity of v. For example, if v is 0.5, the child will be
  /// blended 50% with its background. Similarly, if v is 0.0, the child will be
  /// completely transparent.
283
  Animation<double> get opacity => listenable;
284

285
  /// The widget below this widget in the tree.
286 287
  final Widget child;

288
  @override
289 290 291 292 293
  Widget build(BuildContext context) {
    return new Opacity(opacity: opacity.value, child: child);
  }
}

294
/// An interpolation between two relative rects.
295
///
296
/// This class specializes the interpolation of [Tween<RelativeRect>] to
297
/// use [RelativeRect.lerp].
298 299
///
/// See [Tween] for a discussion on how to use interpolation objects.
300
class RelativeRectTween extends Tween<RelativeRect> {
301
  /// Creates a [RelativeRect] tween.
302
  ///
303 304
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as [RelativeRect.fill].
305 306
  RelativeRectTween({ RelativeRect begin, RelativeRect end })
    : super(begin: begin, end: end);
307

308
  /// Returns the value this variable has at the given animation clock value.
309
  @override
310 311 312
  RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t);
}

313
/// Animated version of [Positioned] which takes a specific
314 315
/// [Animation<RelativeRect>] to transition the child's position from a start
/// position to and end position over the lifetime of the animation.
316
///
317
/// Only works if it's the child of a [Stack].
318 319 320
///
/// See also:
///
321
/// * [RelativePositionedTransition].
322
class PositionedTransition extends AnimatedWidget {
323 324
  /// Creates a transition for [Positioned].
  ///
325
  /// The [rect] argument must not be null.
326
  const PositionedTransition({
327
    Key key,
328
    @required Animation<RelativeRect> rect,
329
    @required this.child,
330
  }) : super(key: key, listenable: rect);
331

332
  /// The animation that controls the child's size and position.
333
  Animation<RelativeRect> get rect => listenable;
334

335
  /// The widget below this widget in the tree.
336
  final Widget child;
337

338
  @override
339
  Widget build(BuildContext context) {
340 341
    return new Positioned.fromRelativeRect(
      rect: rect.value,
342
      child: child,
343 344 345 346
    );
  }
}

347 348 349 350 351 352 353 354
/// Animated version of [Positioned] which transitions the child's position
/// based on the value of [rect] relative to a bounding box with the
/// specified [size].
///
/// Only works if it's the child of a [Stack].
///
/// See also:
///
355
/// * [PositionedTransition].
356
class RelativePositionedTransition extends AnimatedWidget {
357 358 359 360
  /// Create an animated version of [Positioned].
  ///
  /// Each frame, the [Positioned] widget will be configured to represent the
  /// current value of the [rect] argument assuming that the stack has the given
361
  /// [size]. Both [rect] and [size] must not be null.
362
  const RelativePositionedTransition({
363 364 365
    Key key,
    @required Animation<Rect> rect,
    @required this.size,
366
    @required this.child,
367
  }) : super(key: key, listenable: rect);
368 369

  /// The animation that controls the child's size and position.
370 371
  ///
  /// See also [size].
372
  Animation<Rect> get rect => listenable;
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388

  /// The [Positioned] widget's offsets are relative to a box of this
  /// size whose origin is 0,0.
  final Size size;

  /// The widget below this widget in the tree.
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final RelativeRect offsets = new RelativeRect.fromSize(rect.value, size);
    return new Positioned(
      top: offsets.top,
      right: offsets.right,
      bottom: offsets.bottom,
      left: offsets.left,
389
      child: child,
390 391 392 393
    );
  }
}

394 395
/// Animated version of a [DecoratedBox] that animates the different properties
/// of its [Decoration].
396
///
397 398 399
/// See also:
///
/// * [DecoratedBox], which also draws a [Decoration] but is not animated.
400
/// * [AnimatedContainer], a more full-featured container that also animates on
401 402
///   decoration using an internal animation.
class DecoratedBoxTransition extends AnimatedWidget {
403
  /// Creates an animated [DecoratedBox] whose [Decoration] animation updates
404 405 406 407
  /// the widget.
  ///
  /// The [decoration] and [position] cannot be null.
  ///
408
  /// See also:
409
  ///
410
  /// * [new DecoratedBox].
411
  const DecoratedBoxTransition({
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
    Key key,
    @required this.decoration,
    this.position: DecorationPosition.background,
    @required this.child,
  }) : super(key: key, listenable: decoration);

  /// Animation of the decoration to paint.
  ///
  /// Can be created using a [DecorationTween] interpolating typically between
  /// two [BoxDecoration].
  final Animation<Decoration> decoration;

  /// Whether to paint the box decoration behind or in front of the child.
  final DecorationPosition position;

  /// The widget below this widget in the tree.
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return new DecoratedBox(
      decoration: decoration.value,
      position: position,
      child: child,
    );
  }
}

440
/// A builder that builds a widget given a child.
441 442 443 444
///
/// The child should typically be part of the returned widget tree.
///
/// Used by [AnimatedBuilder.builder].
445 446
typedef Widget TransitionBuilder(BuildContext context, Widget child);

447 448
/// A general-purpose widget for building animations.
///
449
/// AnimatedBuilder is useful for more complex widgets that wish to include
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
/// an animation as part of a larger build function. To use AnimatedBuilder,
/// simply construct the widget and pass it a builder function.
///
/// If your [builder] function contains a subtree that does not depend on the
/// animation, it's more efficient to build that subtree once instead of
/// rebuilding it on every animation tick.
///
/// If you pass the pre-built subtree as the [child] parameter, the
/// AnimatedBuilder will pass it back to your builder function so that you
/// can incorporate it into your build.
///
/// Using this pre-built child is entirely optional, but can improve
/// performance significantly in some cases and is therefore a good practice.
///
/// For simple cases without additional state, consider using
465 466
/// [AnimatedWidget].
class AnimatedBuilder extends AnimatedWidget {
467 468
  /// Creates an animated builder.
  ///
469
  /// The [animation] and [builder] arguments must not be null.
470
  const AnimatedBuilder({
471
    Key key,
472
    @required Listenable animation,
473
    @required this.builder,
474
    this.child,
475 476
  }) : assert(builder != null),
       super(key: key, listenable: animation);
477

478
  /// Called every time the animation changes value.
479
  final TransitionBuilder builder;
480 481 482 483 484 485 486 487 488 489 490

  /// If your builder function contains a subtree that does not depend on the
  /// animation, it's more efficient to build that subtree once instead of
  /// rebuilding it on every animation tick.
  ///
  /// If you pass the pre-built subtree as the [child] parameter, the
  /// AnimatedBuilder will pass it back to your builder function so that you
  /// can incorporate it into your build.
  ///
  /// Using this pre-built child is entirely optional, but can improve
  /// performance significantly in some cases and is therefore a good practice.
491
  final Widget child;
492

493
  @override
494
  Widget build(BuildContext context) {
495
    return builder(context, child);
496 497
  }
}