transitions.dart 12.8 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:meta/meta.dart';
Hixie's avatar
Hixie committed
8
import 'package:vector_math/vector_math_64.dart' show Matrix4;
9

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

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

15
/// A widget that rebuilds when the given animation changes value.
16
///
17 18
/// AnimatedWidget is most useful for stateless animated widgets. To use
/// AnimatedWidget, simply subclass it and implement the build function.
19 20 21
///
/// For more complex case involving additional state, consider using
/// [AnimatedBuilder].
22
abstract class AnimatedWidget extends StatefulWidget {
23 24
  /// Creates a widget that rebuilds when the given animation changes value.
  ///
25
  /// The [animation] argument is required.
26
  AnimatedWidget({
27
    Key key,
28
    @required this.animation
29
  }) : super(key: key) {
30
    assert(animation != null);
31 32
  }

33
  /// The animation to which this widget is listening.
34
  final Animation<Object> animation;
35

36
  /// Override this method to build widgets that depend on the current value
37
  /// of the animation.
38 39
  Widget build(BuildContext context);

40
  /// Subclasses typically do not override this method.
41
  @override
42
  _AnimatedState createState() => new _AnimatedState();
43

44
  @override
45 46
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
47
    description.add('animation: $animation');
48 49 50
  }
}

51
class _AnimatedState extends State<AnimatedWidget> {
52
  @override
53 54
  void initState() {
    super.initState();
55
    config.animation.addListener(_handleTick);
56 57
  }

58
  @override
59
  void didUpdateConfig(AnimatedWidget oldConfig) {
60 61 62
    if (config.animation != oldConfig.animation) {
      oldConfig.animation.removeListener(_handleTick);
      config.animation.addListener(_handleTick);
63 64 65
    }
  }

66
  @override
67
  void dispose() {
68
    config.animation.removeListener(_handleTick);
69 70 71 72 73
    super.dispose();
  }

  void _handleTick() {
    setState(() {
74
      // The animation's state is our build state, and it changed already.
75 76 77
    });
  }

78
  @override
79 80 81 82 83
  Widget build(BuildContext context) {
    return config.build(context);
  }
}

84
/// Animates the position of a widget relative to its normal position.
85
class SlideTransition extends AnimatedWidget {
86 87 88
  /// Creates a fractional translation transition.
  ///
  /// The [position] argument is required.
89
  SlideTransition({
90
    Key key,
91
    Animation<FractionalOffset> position,
92
    this.transformHitTests: true,
93
    this.child
94
  }) : super(key: key, animation: position);
95

96 97 98 99
  /// 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.
100
  Animation<FractionalOffset> get position => animation;
101 102 103 104 105 106 107

  /// 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".
108
  final bool transformHitTests;
109

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

113
  @override
114 115 116 117 118 119
  Widget build(BuildContext context) {
    return new FractionalTranslation(
      translation: position.value,
      transformHitTests: transformHitTests,
      child: child
    );
120 121 122
  }
}

Hans Muller's avatar
Hans Muller committed
123
/// Animates the scale of transformed widget.
124
class ScaleTransition extends AnimatedWidget {
125 126 127 128
  /// Creates a scale transition.
  ///
  /// The [scale] argument is required. The [alignment] argument defaults to
  /// [FractionalOffset.center].
129 130
  ScaleTransition({
    Key key,
131
    Animation<double> scale,
132
    this.alignment: FractionalOffset.center,
133
    this.child
134
  }) : super(key: key, animation: scale);
135

136 137 138 139
  /// 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.
140
  Animation<double> get scale => animation;
141 142 143 144 145 146

  /// 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).
147
  final FractionalOffset alignment;
148

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

152
  @override
153
  Widget build(BuildContext context) {
154
    final double scaleValue = scale.value;
155
    Matrix4 transform = new Matrix4.identity()
156
      ..scale(scaleValue, scaleValue, 1.0);
157 158 159 160 161 162 163 164
    return new Transform(
      transform: transform,
      alignment: alignment,
      child: child
    );
  }
}

165
/// Animates the rotation of a widget.
166
class RotationTransition extends AnimatedWidget {
167 168 169
  /// Creates a rotation transition.
  ///
  /// The [turns] argument is required.
Hixie's avatar
Hixie committed
170 171
  RotationTransition({
    Key key,
172
    Animation<double> turns,
173
    this.child
174
  }) : super(key: key, animation: turns);
Hixie's avatar
Hixie committed
175

176 177 178 179
  /// 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.
180
  Animation<double> get turns => animation;
181

182
  /// The widget below this widget in the tree.
183
  final Widget child;
Hixie's avatar
Hixie committed
184

185
  @override
186 187 188
  Widget build(BuildContext context) {
    double turnsValue = turns.value;
    Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.PI * 2.0);
Hixie's avatar
Hixie committed
189 190
    return new Transform(
      transform: transform,
191
      alignment: FractionalOffset.center,
Hixie's avatar
Hixie committed
192 193 194 195 196
      child: child
    );
  }
}

197
/// Animates its own size and clips and aligns the child.
198
class SizeTransition extends AnimatedWidget {
199 200 201 202 203
  /// Creates a size transition.
  ///
  /// The [sizeFactor] argument is required. The [axis] argument defaults to
  /// [Axis.vertical]. The [axisAlignment] defaults to 0.5, which centers the
  /// child along the main axis during the transition.
Hans Muller's avatar
Hans Muller committed
204 205 206 207
  SizeTransition({
    Key key,
    this.axis: Axis.vertical,
    Animation<double> sizeFactor,
208
    this.axisAlignment: 0.5,
Hans Muller's avatar
Hans Muller committed
209
    this.child
210
  }) : super(key: key, animation: sizeFactor) {
Hans Muller's avatar
Hans Muller committed
211 212 213 214 215 216 217 218 219
    assert(axis != null);
  }

  /// [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.
220
  Animation<double> get sizeFactor => animation;
Hans Muller's avatar
Hans Muller committed
221

222 223
  /// How to align the child along the axis that sizeFactor is modifying.
  final double axisAlignment;
Hans Muller's avatar
Hans Muller committed
224

225
  /// The widget below this widget in the tree.
Hans Muller's avatar
Hans Muller committed
226 227
  final Widget child;

228
  @override
Hans Muller's avatar
Hans Muller committed
229
  Widget build(BuildContext context) {
230 231 232 233 234
    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
235 236 237 238 239 240 241 242 243 244 245
    return new ClipRect(
      child: new Align(
        alignment: alignment,
        heightFactor: axis == Axis.vertical ? sizeFactor.value : null,
        widthFactor: axis == Axis.horizontal ? sizeFactor.value : null,
        child: child
      )
    );
  }
}

246
/// Animates the opacity of a widget.
247
class FadeTransition extends AnimatedWidget {
248 249 250
  /// Creates an opacity transition.
  ///
  /// The [opacity] argument is required.
251 252
  FadeTransition({
    Key key,
253
    Animation<double> opacity,
254
    this.child
255
  }) : super(key: key, animation: opacity);
256

257 258 259 260 261 262
  /// 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.
263
  Animation<double> get opacity => animation;
264

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

268
  @override
269 270 271 272 273
  Widget build(BuildContext context) {
    return new Opacity(opacity: opacity.value, child: child);
  }
}

274
/// An interpolation between two relative rects.
275
///
276 277
/// This class specializes the interpolation of Tween<RelativeRect> to be
/// appropriate for rectangles that are described in terms of offsets from
278
/// other rectangles.
279
class RelativeRectTween extends Tween<RelativeRect> {
280 281 282
  /// Creates a relative rect tween.
  ///
  /// The [begin] and [end] arguments must not be null.
283 284
  RelativeRectTween({ RelativeRect begin, RelativeRect end })
    : super(begin: begin, end: end);
285

286
  @override
287 288 289
  RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t);
}

290
/// Animated version of [Positioned] which takes a specific
291 292
/// [Animation<RelativeRect>] to transition the child's position from a start
/// position to and end position over the lifetime of the animation.
293
///
294
/// Only works if it's the child of a [Stack].
295 296 297 298
///
/// See also:
///
/// * [RelativePositionedTransition]
299
class PositionedTransition extends AnimatedWidget {
300 301 302
  /// Creates a transition for [Positioned].
  ///
  /// The [rect] argument is required.
303 304
  PositionedTransition({
    Key key,
305
    Animation<RelativeRect> rect,
306
    this.child
307
  }) : super(key: key, animation: rect);
308

309
  /// The animation that controls the child's size and position.
310
  Animation<RelativeRect> get rect => animation;
311

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

315
  @override
316
  Widget build(BuildContext context) {
317 318 319 320 321 322 323 324 325 326
    return new Positioned(
      top: rect.value.top,
      right: rect.value.right,
      bottom: rect.value.bottom,
      left: rect.value.left,
      child: child
    );
  }
}

327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
/// 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:
///
/// * [PositionedTransition]
class RelativePositionedTransition extends AnimatedWidget {
  RelativePositionedTransition({
    Key key,
    @required Animation<Rect> rect,
    @required this.size,
    this.child
  }) : super(key: key, animation: rect);

  /// The animation that controls the child's size and position.
  Animation<Rect> get rect => animation;

  /// 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,
      child: child
    );
  }
}

367
/// A builder that builds a widget given a child.
368 369
typedef Widget TransitionBuilder(BuildContext context, Widget child);

370 371
/// A general-purpose widget for building animations.
///
372
/// AnimatedBuilder is useful for more complex widgets that wish to include
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
/// 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
388 389
/// [AnimatedWidget].
class AnimatedBuilder extends AnimatedWidget {
390 391
  /// Creates an animated builder.
  ///
392
  /// The [animation] and [builder] arguments must not be null.
393
  AnimatedBuilder({
394
    Key key,
395 396
    @required Animation<Object> animation,
    @required this.builder,
397
    this.child
398 399 400
  }) : super(key: key, animation: animation) {
    assert(builder != null);
  }
401

402
  /// Called every time the animation changes value.
403
  final TransitionBuilder builder;
404 405 406 407 408 409 410 411 412 413 414

  /// 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.
415
  final Widget child;
416

417
  @override
418
  Widget build(BuildContext context) {
419
    return builder(context, child);
420 421
  }
}