implicit_animations.dart 20 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
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.

5
import 'package:flutter/foundation.dart';
6 7
import 'package:vector_math/vector_math_64.dart';

8
import 'basic.dart';
9
import 'container.dart';
10
import 'framework.dart';
11
import 'text.dart';
12
import 'ticker_provider.dart';
Adam Barth's avatar
Adam Barth committed
13

14
/// An interpolation between two [BoxConstraint]s.
15
class BoxConstraintsTween extends Tween<BoxConstraints> {
16 17 18
  /// Creates a box constraints tween.
  ///
  /// The [begin] and [end] arguments must not be null.
19
  BoxConstraintsTween({ BoxConstraints begin, BoxConstraints end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
20

21
  @override
Adam Barth's avatar
Adam Barth committed
22 23 24
  BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t);
}

25
/// An interpolation between two [Decoration]s.
26
class DecorationTween extends Tween<Decoration> {
27 28 29
  /// Creates a decoration tween.
  ///
  /// The [begin] and [end] arguments must not be null.
30
  DecorationTween({ Decoration begin, Decoration end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
31

32
  @override
33
  Decoration lerp(double t) => Decoration.lerp(begin, end, t);
Adam Barth's avatar
Adam Barth committed
34 35
}

36 37
/// An interpolation between two [EdgeInsets]s.
class EdgeInsetsTween extends Tween<EdgeInsets> {
38 39 40
  /// Creates an edge insets tween.
  ///
  /// The [begin] and [end] arguments must not be null.
41
  EdgeInsetsTween({ EdgeInsets begin, EdgeInsets end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
42

43
  @override
44
  EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t);
Adam Barth's avatar
Adam Barth committed
45 46
}

47
/// An interpolation between two [Matrix4]s.
48 49
///
/// Currently this class works only for translations.
50
class Matrix4Tween extends Tween<Matrix4> {
51 52 53
  /// Creates a [Matrix4] tween.
  ///
  /// The [begin] and [end] arguments must not be null.
54
  Matrix4Tween({ Matrix4 begin, Matrix4 end }) : super(begin: begin, end: end);
Adam Barth's avatar
Adam Barth committed
55

56
  @override
Adam Barth's avatar
Adam Barth committed
57
  Matrix4 lerp(double t) {
58 59
    // TODO(abarth): We should use [Matrix4.decompose] and animate the
    // decomposed parameters instead of just animating the translation.
Adam Barth's avatar
Adam Barth committed
60 61 62 63 64 65 66
    Vector3 beginT = begin.getTranslation();
    Vector3 endT = end.getTranslation();
    Vector3 lerpT = beginT*(1.0-t) + endT*t;
    return new Matrix4.identity()..translate(lerpT);
  }
}

67 68 69 70
/// An interpolation between two [TextStyle]s.
///
/// This will not work well if the styles don't set the same fields.
class TextStyleTween extends Tween<TextStyle> {
71 72 73
  /// Creates a text style tween.
  ///
  /// The [begin] and [end] arguments must not be null.
74 75 76 77 78 79
  TextStyleTween({ TextStyle begin, TextStyle end }) : super(begin: begin, end: end);

  @override
  TextStyle lerp(double t) => TextStyle.lerp(begin, end, t);
}

80
/// An abstract widget for building widgets that gradually change their
81
/// values over a period of time.
82
abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
83 84 85
  /// Initializes fields for subclasses.
  ///
  /// The [curve] and [duration] arguments must not be null.
86
  ImplicitlyAnimatedWidget({
Adam Barth's avatar
Adam Barth committed
87
    Key key,
88
    this.curve: Curves.linear,
89
    @required this.duration
Adam Barth's avatar
Adam Barth committed
90 91
  }) : super(key: key) {
    assert(curve != null);
Ian Hickson's avatar
Ian Hickson committed
92
    assert(duration != null);
Adam Barth's avatar
Adam Barth committed
93 94
  }

95
  /// The curve to apply when animating the parameters of this container.
Adam Barth's avatar
Adam Barth committed
96
  final Curve curve;
97 98

  /// The duration over which to animate the parameters of this container.
Adam Barth's avatar
Adam Barth committed
99 100
  final Duration duration;

101
  @override
102
  AnimatedWidgetBaseState<ImplicitlyAnimatedWidget> createState();
103

104
  @override
105 106 107 108
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('duration: ${duration.inMilliseconds}ms');
  }
Adam Barth's avatar
Adam Barth committed
109 110
}

111 112 113 114
/// Signature for a [Tween] factory.
///
/// This is the type of one of the arguments of [TweenVisitor], the signature
/// used by [AnimatedWidgetBaseState.forEachTween].
115
typedef Tween<T> TweenConstructor<T>(T targetValue);
116

117
/// Signature for callbacks passed to [AnimatedWidgetBaseState.forEachTween].
118
typedef Tween<T> TweenVisitor<T>(Tween<T> tween, T targetValue, TweenConstructor<T> constructor);
Adam Barth's avatar
Adam Barth committed
119

120
/// A base class for widgets with implicit animations.
121
abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin {
122 123
  AnimationController _controller;

124
  /// The animation driving this widget's implicit animations.
125 126
  Animation<double> get animation => _animation;
  Animation<double> _animation;
Adam Barth's avatar
Adam Barth committed
127

128
  @override
Adam Barth's avatar
Adam Barth committed
129 130
  void initState() {
    super.initState();
131
    _controller = new AnimationController(
132
      duration: config.duration,
133 134
      debugLabel: '${config.toStringShort()}',
      vsync: this,
135
    )..addListener(_handleAnimationChanged);
136
    _updateCurve();
137
    _constructTweens();
Adam Barth's avatar
Adam Barth committed
138 139
  }

140
  @override
141
  void didUpdateConfig(T oldConfig) {
142 143
    if (config.curve != oldConfig.curve)
      _updateCurve();
144 145
    _controller.duration = config.duration;
    if (_constructTweens()) {
146
      forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
147 148
        _updateTween(tween, targetValue);
        return tween;
149
      });
150 151 152
      _controller
        ..value = 0.0
        ..forward();
Adam Barth's avatar
Adam Barth committed
153 154 155
    }
  }

156 157
  void _updateCurve() {
    if (config.curve != null)
158
      _animation = new CurvedAnimation(parent: _controller, curve: config.curve);
159
    else
160
      _animation = _controller;
161 162
  }

163
  @override
Adam Barth's avatar
Adam Barth committed
164
  void dispose() {
165
    _controller.dispose();
Adam Barth's avatar
Adam Barth committed
166 167 168
    super.dispose();
  }

169 170
  void _handleAnimationChanged() {
    setState(() { });
Adam Barth's avatar
Adam Barth committed
171 172
  }

173
  bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) {
174
    return targetValue != (tween.end ?? tween.begin);
175 176
  }

177
  void _updateTween(Tween<dynamic> tween, dynamic targetValue) {
178 179 180 181 182
    if (tween == null)
      return;
    tween
      ..begin = tween.evaluate(_animation)
      ..end = targetValue;
Adam Barth's avatar
Adam Barth committed
183 184
  }

185 186
  bool _constructTweens() {
    bool shouldStartAnimation = false;
187
    forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
188
      if (targetValue != null) {
189 190 191
        tween ??= constructor(targetValue);
        if (_shouldAnimateTween(tween, targetValue))
          shouldStartAnimation = true;
192
      } else {
193
        tween = null;
194
      }
195
      return tween;
196
    });
197
    return shouldStartAnimation;
198
  }
Adam Barth's avatar
Adam Barth committed
199

200
  /// Subclasses must implement this function by running through the following
201
  /// steps for each animatable facet in the class:
202 203
  ///
  /// 1. Call the visitor callback with three arguments, the first argument
204 205
  /// being the current value of the Tween<T> object that represents the
  /// tween (initially null), the second argument, of type T, being the value
206
  /// on the Widget (config) that represents the current target value of the
207
  /// tween, and the third being a callback that takes a value T (which will
208
  /// be the second argument to the visitor callback), and that returns an
209
  /// Tween<T> object for the tween, configured with the given value
210 211 212
  /// as the begin value.
  ///
  /// 2. Take the value returned from the callback, and store it. This is the
213
  /// value to use as the current value the next time that the forEachTween()
214
  /// method is called.
215
  void forEachTween(TweenVisitor<dynamic> visitor);
216
}
Adam Barth's avatar
Adam Barth committed
217

218 219 220 221
/// A container that gradually changes its values over a period of time.
///
/// This class is useful for generating simple implicit transitions between
/// different parameters to [Container]. For more complex animations, you'll
222 223
/// likely want to use a subclass of [Transition] or use an
/// [AnimationController] yourself.
224 225
///
/// Properties that are null are not animated.
226
class AnimatedContainer extends ImplicitlyAnimatedWidget {
227 228 229
  /// Creates a container that animates its parameters implicitly.
  ///
  /// The [curve] and [duration] arguments must not be null.
230 231 232 233 234 235 236 237 238 239 240 241
  AnimatedContainer({
    Key key,
    this.child,
    this.constraints,
    this.decoration,
    this.foregroundDecoration,
    this.margin,
    this.padding,
    this.transform,
    this.width,
    this.height,
    Curve curve: Curves.linear,
242
    @required Duration duration,
243
  }) : super(key: key, curve: curve, duration: duration) {
244 245
    assert(decoration == null || decoration.debugAssertIsValid());
    assert(foregroundDecoration == null || foregroundDecoration.debugAssertIsValid());
246 247
    assert(margin == null || margin.isNonNegative);
    assert(padding == null || padding.isNonNegative);
248
    assert(constraints == null || constraints.debugAssertIsValid());
249
  }
Adam Barth's avatar
Adam Barth committed
250

251
  /// The widget below this widget in the tree.
252
  final Widget child;
Adam Barth's avatar
Adam Barth committed
253

254 255
  /// Additional constraints to apply to the child.
  final BoxConstraints constraints;
Adam Barth's avatar
Adam Barth committed
256

257 258
  /// The decoration to paint behind the child.
  final Decoration decoration;
Adam Barth's avatar
Adam Barth committed
259

260 261
  /// The decoration to paint in front of the child.
  final Decoration foregroundDecoration;
Adam Barth's avatar
Adam Barth committed
262

263
  /// Empty space to surround the decoration.
264
  final EdgeInsets margin;
Adam Barth's avatar
Adam Barth committed
265

266
  /// Empty space to inscribe inside the decoration.
267
  final EdgeInsets padding;
268 269 270 271 272 273 274 275 276 277

  /// The transformation matrix to apply before painting the container.
  final Matrix4 transform;

  /// If non-null, requires the decoration to have this width.
  final double width;

  /// If non-null, requires the decoration to have this height.
  final double height;

278
  @override
279
  _AnimatedContainerState createState() => new _AnimatedContainerState();
280

281
  @override
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (constraints != null)
      description.add('$constraints');
    if (decoration != null)
      description.add('has background');
    if (foregroundDecoration != null)
      description.add('has foreground');
    if (margin != null)
      description.add('margin: $margin');
    if (padding != null)
      description.add('padding: $padding');
    if (transform != null)
      description.add('has transform');
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
  }
301 302 303
}

class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
304 305 306
  BoxConstraintsTween _constraints;
  DecorationTween _decoration;
  DecorationTween _foregroundDecoration;
307 308
  EdgeInsetsTween _margin;
  EdgeInsetsTween _padding;
309 310 311 312
  Matrix4Tween _transform;
  Tween<double> _width;
  Tween<double> _height;

313
  @override
314
  void forEachTween(TweenVisitor<dynamic> visitor) {
315
    // TODO(ianh): Use constructor tear-offs when it becomes possible
316 317 318
    _constraints = visitor(_constraints, config.constraints, (dynamic value) => new BoxConstraintsTween(begin: value));
    _decoration = visitor(_decoration, config.decoration, (dynamic value) => new DecorationTween(begin: value));
    _foregroundDecoration = visitor(_foregroundDecoration, config.foregroundDecoration, (dynamic value) => new DecorationTween(begin: value));
319 320
    _margin = visitor(_margin, config.margin, (dynamic value) => new EdgeInsetsTween(begin: value));
    _padding = visitor(_padding, config.padding, (dynamic value) => new EdgeInsetsTween(begin: value));
321 322 323
    _transform = visitor(_transform, config.transform, (dynamic value) => new Matrix4Tween(begin: value));
    _width = visitor(_width, config.width, (dynamic value) => new Tween<double>(begin: value));
    _height = visitor(_height, config.height, (dynamic value) => new Tween<double>(begin: value));
Adam Barth's avatar
Adam Barth committed
324 325
  }

326
  @override
Adam Barth's avatar
Adam Barth committed
327 328 329
  Widget build(BuildContext context) {
    return new Container(
      child: config.child,
330 331 332 333 334 335 336 337
      constraints: _constraints?.evaluate(animation),
      decoration: _decoration?.evaluate(animation),
      foregroundDecoration: _foregroundDecoration?.evaluate(animation),
      margin: _margin?.evaluate(animation),
      padding: _padding?.evaluate(animation),
      transform: _transform?.evaluate(animation),
      width: _width?.evaluate(animation),
      height: _height?.evaluate(animation)
Adam Barth's avatar
Adam Barth committed
338 339
    );
  }
Hixie's avatar
Hixie committed
340

341
  @override
Hixie's avatar
Hixie committed
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (_constraints != null)
      description.add('has constraints');
    if (_decoration != null)
      description.add('has background');
    if (_foregroundDecoration != null)
      description.add('has foreground');
    if (_margin != null)
      description.add('has margin');
    if (_padding != null)
      description.add('has padding');
    if (_transform != null)
      description.add('has transform');
    if (_width != null)
      description.add('has width');
    if (_height != null)
      description.add('has height');
  }
Adam Barth's avatar
Adam Barth committed
361
}
Ian Hickson's avatar
Ian Hickson committed
362 363

/// Animated version of [Positioned] which automatically transitions the child's
364
/// position over a given duration whenever the given position changes.
Ian Hickson's avatar
Ian Hickson committed
365 366
///
/// Only works if it's the child of a [Stack].
367
class AnimatedPositioned extends ImplicitlyAnimatedWidget {
368 369 370 371 372 373 374 375
  /// Creates a widget that animates its position implicitly.
  ///
  /// Only two out of the three horizontal values ([left], [right],
  /// [width]), and only two out of the three vertical values ([top],
  /// [bottom], [height]), can be set. In each case, at least one of
  /// the three must be null.
  ///
  /// The [curve] and [duration] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
376 377
  AnimatedPositioned({
    Key key,
378
    @required this.child,
Ian Hickson's avatar
Ian Hickson committed
379 380 381 382 383 384 385
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    Curve curve: Curves.linear,
386
    @required Duration duration,
Ian Hickson's avatar
Ian Hickson committed
387 388 389 390 391
  }) : super(key: key, curve: curve, duration: duration) {
    assert(left == null || right == null || width == null);
    assert(top == null || bottom == null || height == null);
  }

392 393 394
  /// Creates a widget that animates the rectangle it occupies implicitly.
  ///
  /// The [curve] and [duration] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
395 396 397 398 399
  AnimatedPositioned.fromRect({
    Key key,
    this.child,
    Rect rect,
    Curve curve: Curves.linear,
400
    @required Duration duration
Ian Hickson's avatar
Ian Hickson committed
401 402 403 404 405 406 407 408
  }) : left = rect.left,
       top = rect.top,
       width = rect.width,
       height = rect.height,
       right = null,
       bottom = null,
       super(key: key, curve: curve, duration: duration);

409
  /// The widget below this widget in the tree.
Ian Hickson's avatar
Ian Hickson committed
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
  final Widget child;

  /// The offset of the child's left edge from the left of the stack.
  final double left;

  /// The offset of the child's top edge from the top of the stack.
  final double top;

  /// The offset of the child's right edge from the right of the stack.
  final double right;

  /// The offset of the child's bottom edge from the bottom of the stack.
  final double bottom;

  /// The child's width.
  ///
  /// Only two out of the three horizontal values (left, right, width) can be
  /// set. The third must be null.
  final double width;

  /// The child's height.
  ///
  /// Only two out of the three vertical values (top, bottom, height) can be
  /// set. The third must be null.
  final double height;

436
  @override
Ian Hickson's avatar
Ian Hickson committed
437
  _AnimatedPositionedState createState() => new _AnimatedPositionedState();
Ian Hickson's avatar
Ian Hickson committed
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454

  @override
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (left != null)
      description.add('left: $left');
    if (top != null)
      description.add('top: $top');
    if (right != null)
      description.add('right: $right');
    if (bottom != null)
      description.add('bottom: $bottom');
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
  }
Ian Hickson's avatar
Ian Hickson committed
455 456 457
}

class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositioned> {
458 459 460 461 462 463 464
  Tween<double> _left;
  Tween<double> _top;
  Tween<double> _right;
  Tween<double> _bottom;
  Tween<double> _width;
  Tween<double> _height;

465
  @override
466
  void forEachTween(TweenVisitor<dynamic> visitor) {
Ian Hickson's avatar
Ian Hickson committed
467
    // TODO(ianh): Use constructor tear-offs when it becomes possible
468 469 470 471 472 473
    _left = visitor(_left, config.left, (dynamic value) => new Tween<double>(begin: value));
    _top = visitor(_top, config.top, (dynamic value) => new Tween<double>(begin: value));
    _right = visitor(_right, config.right, (dynamic value) => new Tween<double>(begin: value));
    _bottom = visitor(_bottom, config.bottom, (dynamic value) => new Tween<double>(begin: value));
    _width = visitor(_width, config.width, (dynamic value) => new Tween<double>(begin: value));
    _height = visitor(_height, config.height, (dynamic value) => new Tween<double>(begin: value));
Ian Hickson's avatar
Ian Hickson committed
474 475
  }

476
  @override
Ian Hickson's avatar
Ian Hickson committed
477 478 479
  Widget build(BuildContext context) {
    return new Positioned(
      child: config.child,
480 481 482 483 484 485
      left: _left?.evaluate(animation),
      top: _top?.evaluate(animation),
      right: _right?.evaluate(animation),
      bottom: _bottom?.evaluate(animation),
      width: _width?.evaluate(animation),
      height: _height?.evaluate(animation)
Ian Hickson's avatar
Ian Hickson committed
486 487 488
    );
  }

489
  @override
Ian Hickson's avatar
Ian Hickson committed
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (_left != null)
      description.add('has left');
    if (_top != null)
      description.add('has top');
    if (_right != null)
      description.add('has right');
    if (_bottom != null)
      description.add('has bottom');
    if (_width != null)
      description.add('has width');
    if (_height != null)
      description.add('has height');
  }
}
Ian Hickson's avatar
Ian Hickson committed
506 507 508 509 510 511

/// Animated version of [Opacity] which automatically transitions the child's
/// opacity over a given duration whenever the given opacity changes.
///
/// Animating an opacity is relatively expensive.
class AnimatedOpacity extends ImplicitlyAnimatedWidget {
512 513 514 515
  /// Creates a widget that animates its opacity implicitly.
  ///
  /// The [opacity] argument must not be null and must be between 0.0 and 1.0,
  /// inclusive. The [curve] and [duration] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
516 517 518 519 520
  AnimatedOpacity({
    Key key,
    this.child,
    this.opacity,
    Curve curve: Curves.linear,
521
    @required Duration duration,
Ian Hickson's avatar
Ian Hickson committed
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
  }) : super(key: key, curve: curve, duration: duration) {
    assert(opacity != null && opacity >= 0.0 && opacity <= 1.0);
  }

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

  /// The target opacity.
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
  ///
  /// The opacity must not be null.
  final double opacity;

  @override
  _AnimatedOpacityState createState() => new _AnimatedOpacityState();

  @override
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('opacity: $opacity');
  }
}

class _AnimatedOpacityState extends AnimatedWidgetBaseState<AnimatedOpacity> {
  Tween<double> _opacity;

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    // TODO(ianh): Use constructor tear-offs when it becomes possible
    _opacity = visitor(_opacity, config.opacity, (dynamic value) => new Tween<double>(begin: value));
  }

  @override
  Widget build(BuildContext context) {
    return new Opacity(
      opacity: _opacity.evaluate(animation),
      child: config.child
    );
  }
}
564 565 566 567 568 569

/// Animated version of [DefaultTextStyle] which automatically
/// transitions the default text style (the text style to apply to
/// descendant [Text] widgets without explicit style) over a given
/// duration whenever the given style changes.
class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget {
570 571 572
  /// Creates a widget that animates the default text style implicitly.
  ///
  /// The [child], [style], [curve], and [duration] arguments must not be null.
573 574
  AnimatedDefaultTextStyle({
    Key key,
575 576
    @required this.child,
    @required this.style,
577
    Curve curve: Curves.linear,
578
    @required Duration duration,
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
  }) : super(key: key, curve: curve, duration: duration) {
    assert(style != null);
    assert(child != null);
  }

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

  /// The target text style.
  ///
  /// The text style must not be null.
  final TextStyle style;

  @override
  _AnimatedDefaultTextStyleState createState() => new _AnimatedDefaultTextStyleState();

  @override
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    '$style'.split('\n').forEach(description.add);
  }
}

class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDefaultTextStyle> {
  TextStyleTween _style;

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    // TODO(ianh): Use constructor tear-offs when it becomes possible
    _style = visitor(_style, config.style, (dynamic value) => new TextStyleTween(begin: value));
  }

  @override
  Widget build(BuildContext context) {
613
    return new DefaultTextStyle(
614 615 616 617 618
      style: _style.evaluate(animation),
      child: config.child
    );
  }
}