transitions.dart 8.99 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 7
import 'dart:math' as math;

import 'package:vector_math/vector_math_64.dart' show Matrix4;
8

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

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

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

30
  /// The animation to which this component is listening.
31
  final Animation<Object> animation;
32

33 34
  /// Override this function to build widgets that depend on the current value
  /// of the animation.
35 36
  Widget build(BuildContext context);

37
  /// Subclasses typically do not override this method.
38
  _AnimatedComponentState createState() => new _AnimatedComponentState();
39 40 41

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
42
    description.add('animation: $animation');
43 44 45
  }
}

46
class _AnimatedComponentState extends State<AnimatedComponent> {
47 48
  void initState() {
    super.initState();
49
    config.animation.addListener(_handleTick);
50 51
  }

52 53 54 55
  void didUpdateConfig(AnimatedComponent oldConfig) {
    if (config.animation != oldConfig.animation) {
      oldConfig.animation.removeListener(_handleTick);
      config.animation.addListener(_handleTick);
56 57 58 59
    }
  }

  void dispose() {
60
    config.animation.removeListener(_handleTick);
61 62 63 64 65
    super.dispose();
  }

  void _handleTick() {
    setState(() {
66
      // The animation's state is our build state, and it changed already.
67 68 69 70 71 72 73 74
    });
  }

  Widget build(BuildContext context) {
    return config.build(context);
  }
}

75
/// Animates the position of a widget relative to its normal position.
76
class SlideTransition extends AnimatedComponent {
77
  SlideTransition({
78
    Key key,
79
    Animation<FractionalOffset> position,
80
    this.transformHitTests: true,
81
    this.child
82 83 84
  }) : position = position, super(key: key, animation: position) {
    assert(position != null);
  }
85

86 87 88 89
  /// 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.
90
  final Animation<FractionalOffset> position;
91 92 93 94 95 96 97

  /// 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".
98
  final bool transformHitTests;
99

100
  final Widget child;
101

102 103 104 105 106 107
  Widget build(BuildContext context) {
    return new FractionalTranslation(
      translation: position.value,
      transformHitTests: transformHitTests,
      child: child
    );
108 109 110
  }
}

111
/// Animates the size of a widget.
112
class ScaleTransition extends AnimatedComponent {
113 114
  ScaleTransition({
    Key key,
115
    Animation<double> scale,
116
    this.alignment: const FractionalOffset(0.5, 0.5),
117 118
    this.child
  }) : scale = scale, super(key: key, animation: scale);
119

120 121 122 123
  /// 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.
124
  final Animation<double> scale;
125 126 127 128 129 130

  /// 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).
131
  final FractionalOffset alignment;
132

133
  final Widget child;
134

135 136
  Widget build(BuildContext context) {
    double scaleValue = scale.value;
137
    Matrix4 transform = new Matrix4.identity()
138
      ..scale(scaleValue, scaleValue);
139 140 141 142 143 144 145 146
    return new Transform(
      transform: transform,
      alignment: alignment,
      child: child
    );
  }
}

147
/// Animates the rotation of a widget.
148
class RotationTransition extends AnimatedComponent {
Hixie's avatar
Hixie committed
149 150
  RotationTransition({
    Key key,
151
    Animation<double> turns,
152 153
    this.child
  }) : turns = turns, super(key: key, animation: turns);
Hixie's avatar
Hixie committed
154

155 156 157 158
  /// 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.
159
  final Animation<double> turns;
160

161
  final Widget child;
Hixie's avatar
Hixie committed
162

163 164 165
  Widget build(BuildContext context) {
    double turnsValue = turns.value;
    Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.PI * 2.0);
Hixie's avatar
Hixie committed
166 167 168 169 170 171 172 173
    return new Transform(
      transform: transform,
      alignment: const FractionalOffset(0.5, 0.5),
      child: child
    );
  }
}

174
/// Animates the opacity of a widget.
175 176 177
class FadeTransition extends AnimatedComponent {
  FadeTransition({
    Key key,
178
    Animation<double> opacity,
179 180 181
    this.child
  }) : opacity = opacity, super(key: key, animation: opacity);

182 183 184 185 186 187
  /// 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.
188
  final Animation<double> opacity;
189

190 191 192 193 194 195 196
  final Widget child;

  Widget build(BuildContext context) {
    return new Opacity(opacity: opacity.value, child: child);
  }
}

197
/// An interpolation between two relative rects.
198
///
199 200
/// This class specializes the interpolation of Tween<RelativeRect> to be
/// appropriate for rectangles that are described in terms of offsets from
201
/// other rectangles.
202 203 204
class RelativeRectTween extends Tween<RelativeRect> {
  RelativeRectTween({ RelativeRect begin, RelativeRect end })
    : super(begin: begin, end: end);
205 206 207 208

  RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t);
}

209
/// Animated version of [Positioned] which takes a specific
210 211
/// [Animation<RelativeRect>] to transition the child's position from a start
/// position to and end position over the lifetime of the animation.
212
///
213
/// Only works if it's the child of a [Stack].
214
class PositionedTransition extends AnimatedComponent {
215 216
  PositionedTransition({
    Key key,
217
    Animation<RelativeRect> rect,
218 219
    this.child
  }) : rect = rect, super(key: key, animation: rect) {
220 221 222
    assert(rect != null);
  }

223
  /// The animation that controls the child's size and position.
224
  final Animation<RelativeRect> rect;
225

226
  final Widget child;
227

228
  Widget build(BuildContext context) {
229 230 231 232 233 234 235 236 237 238
    return new Positioned(
      top: rect.value.top,
      right: rect.value.right,
      bottom: rect.value.bottom,
      left: rect.value.left,
      child: child
    );
  }
}

239
/// A builder that builds a widget given a child.
240 241
typedef Widget TransitionBuilder(BuildContext context, Widget child);

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
/// A general-purpose widget for building animations.
///
/// AnimatedBuilder is useful for more complex components that wish to include
/// 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
/// [AnimatedComponent].
261 262
class AnimatedBuilder extends AnimatedComponent {
  AnimatedBuilder({
263
    Key key,
264
    Animation<Object> animation,
265 266
    this.builder,
    this.child
267
  }) : super(key: key, animation: animation);
268

269
  /// Called every time the animation changes value.
270
  final TransitionBuilder builder;
271 272 273 274 275 276 277 278 279 280 281

  /// 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.
282
  final Widget child;
283 284

  Widget build(BuildContext context) {
285
    return builder(context, child);
286 287
  }
}