Animation API improvements (#21540)

diff --git a/dev/bots/analyze-sample-code.dart b/dev/bots/analyze-sample-code.dart
index 341e03e..e4e643a 100644
--- a/dev/bots/analyze-sample-code.dart
+++ b/dev/bots/analyze-sample-code.dart
@@ -267,7 +267,7 @@
           throw 'failed to parse error message (read line number as $lineNumber; total number of lines is ${lines.length}): $error';
         }
         final Line actualLine = lines[lineNumber - 1];
-        if (errorCode == 'unused_element') {
+        if (errorCode == 'unused_element' || errorCode == 'unused_local_variable') {
           // We don't really care if sample code isn't used!
         } else if (actualLine == null) {
           if (errorCode == 'missing_identifier' && lineNumber > 1 && buffer[lineNumber - 2].endsWith(',')) {
@@ -330,6 +330,9 @@
     sections.add(Section(line, 'Future<Null> expression$_expressionId() async { ', block.toList(), ' }'));
   } else if (block.first.startsWith('class ') || block.first.startsWith('enum ')) {
     sections.add(Section(line, null, block.toList(), null));
+  } else if ((block.first.startsWith('_') || block.first.startsWith('final ')) && block.first.contains(' = ')) {
+    _expressionId += 1;
+    sections.add(Section(line, 'void expression$_expressionId() { ', block.toList(), ' }'));
   } else {
     final List<String> buffer = <String>[];
     int subblocks = 0;
diff --git a/dev/docs/survey.html b/dev/docs/survey.html
index 8f022a6..096e19b 100644
--- a/dev/docs/survey.html
+++ b/dev/docs/survey.html
@@ -1,3 +1,3 @@
-<script async="" defer="" 
+<script async="" defer=""
   src="//www.google.com/insights/consumersurveys/async_survey?site=i2bdmo2dzky7fqws25nicgx5f4">
-</script>
\ No newline at end of file
+</script>
diff --git a/dev/integration_tests/flavors/ios/Runner/AppDelegate.m b/dev/integration_tests/flavors/ios/Runner/AppDelegate.m
index 4082691..11b4078 100644
--- a/dev/integration_tests/flavors/ios/Runner/AppDelegate.m
+++ b/dev/integration_tests/flavors/ios/Runner/AppDelegate.m
@@ -8,7 +8,7 @@
   // Override point for customization after application launch.
   FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
   FlutterMethodChannel* flavorChannel = [FlutterMethodChannel methodChannelWithName:@"flavor" binaryMessenger:controller];
-  
+
   [flavorChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
     NSString* flavor = (NSString*)[[NSBundle mainBundle].infoDictionary valueForKey:@"Flavor"];
     result(flavor);
diff --git a/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m b/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m
index 45c58da..5c030e5 100644
--- a/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m
+++ b/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m
@@ -15,7 +15,7 @@
 
 - (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated {
   FlutterViewController* root = (FlutterViewController*)[self.viewControllers objectAtIndex:0];
-  
+
   FlutterBasicMessageChannel* messageChannel =
       [FlutterBasicMessageChannel messageChannelWithName:@"navigation-test"
                                          binaryMessenger:root
diff --git a/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart b/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart
index b0d6c0e..9f7b624 100644
--- a/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart
@@ -318,18 +318,15 @@
     final Size panelSize = constraints.biggest;
     final double panelTop = panelSize.height - panelTitleHeight;
 
-    final Animation<RelativeRect> panelAnimation = RelativeRectTween(
-      begin: RelativeRect.fromLTRB(
-        0.0,
-        panelTop - MediaQuery.of(context).padding.bottom,
-        0.0,
-        panelTop - panelSize.height,
-      ),
-      end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
-    ).animate(
-      CurvedAnimation(
-        parent: _controller,
-        curve: Curves.linear,
+    final Animation<RelativeRect> panelAnimation = _controller.drive(
+      RelativeRectTween(
+        begin: RelativeRect.fromLTRB(
+          0.0,
+          panelTop - MediaQuery.of(context).padding.bottom,
+          0.0,
+          panelTop - panelSize.height,
+        ),
+        end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
       ),
     );
 
diff --git a/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart b/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart
index 9264ff6..85856c1 100644
--- a/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart
@@ -24,10 +24,9 @@
          duration: kThemeAnimationDuration,
          vsync: vsync,
        ) {
-    _animation = CurvedAnimation(
-      parent: controller,
+    _animation = controller.drive(CurveTween(
       curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
-    );
+    ));
   }
 
   final Widget _icon;
@@ -35,7 +34,7 @@
   final String _title;
   final BottomNavigationBarItem item;
   final AnimationController controller;
-  CurvedAnimation _animation;
+  Animation<double> _animation;
 
   FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
     Color iconColor;
@@ -51,10 +50,12 @@
     return FadeTransition(
       opacity: _animation,
       child: SlideTransition(
-        position: Tween<Offset>(
-          begin: const Offset(0.0, 0.02), // Slightly down.
-          end: Offset.zero,
-        ).animate(_animation),
+        position: _animation.drive(
+          Tween<Offset>(
+            begin: const Offset(0.0, 0.02), // Slightly down.
+            end: Offset.zero,
+          ),
+        ),
         child: IconTheme(
           data: IconThemeData(
             color: iconColor,
diff --git a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart
index 19bd0c5..fd868af 100644
--- a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart
@@ -23,6 +23,13 @@
     'A', 'B', 'C', 'D', 'E',
   ];
 
+  static final Animatable<Offset> _drawerDetailsTween = Tween<Offset>(
+    begin: const Offset(0.0, -1.0),
+    end: Offset.zero,
+  ).chain(CurveTween(
+    curve: Curves.fastOutSlowIn,
+  ));
+
   AnimationController _controller;
   Animation<double> _drawerContentsOpacity;
   Animation<Offset> _drawerDetailsPosition;
@@ -39,13 +46,7 @@
       parent: ReverseAnimation(_controller),
       curve: Curves.fastOutSlowIn,
     );
-    _drawerDetailsPosition = Tween<Offset>(
-      begin: const Offset(0.0, -1.0),
-      end: Offset.zero,
-    ).animate(CurvedAnimation(
-      parent: _controller,
-      curve: Curves.fastOutSlowIn,
-    ));
+    _drawerDetailsPosition = _controller.drive(_drawerDetailsTween);
   }
 
   @override
diff --git a/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart b/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart
index 6bf4594..cb9661f 100644
--- a/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart
@@ -117,10 +117,10 @@
       return;
     final Offset direction = details.velocity.pixelsPerSecond / magnitude;
     final double distance = (Offset.zero & context.size).shortestSide;
-    _flingAnimation = Tween<Offset>(
+    _flingAnimation = _controller.drive(Tween<Offset>(
       begin: _offset,
       end: _clampOffset(_offset + direction * distance)
-    ).animate(_controller);
+    ));
     _controller
       ..value = 0.0
       ..fling(velocity: magnitude / 1000.0);
diff --git a/examples/flutter_gallery/lib/demo/material/slider_demo.dart b/examples/flutter_gallery/lib/demo/material/slider_demo.dart
index 5a8d4cc..7306587 100644
--- a/examples/flutter_gallery/lib/demo/material/slider_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/slider_demo.dart
@@ -35,7 +35,7 @@
     return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize);
   }
 
-  static final Tween<double> sizeTween = Tween<double>(
+  static final Animatable<double> sizeTween = Tween<double>(
     begin: _disabledThumbSize,
     end: _thumbSize,
   );
@@ -74,7 +74,7 @@
     return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
   }
 
-  static final Tween<double> sizeTween = Tween<double>(
+  static final Animatable<double> sizeTween = Tween<double>(
     begin: _disabledIndicatorSize,
     end: _indicatorSize,
   );
diff --git a/examples/flutter_gallery/lib/demo/pesto_demo.dart b/examples/flutter_gallery/lib/demo/pesto_demo.dart
index fec72f7..29ed01f 100644
--- a/examples/flutter_gallery/lib/demo/pesto_demo.dart
+++ b/examples/flutter_gallery/lib/demo/pesto_demo.dart
@@ -120,7 +120,7 @@
           final Size size = constraints.biggest;
           final double appBarHeight = size.height - statusBarHeight;
           final double t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);
-          final double extraPadding = Tween<double>(begin: 10.0, end: 24.0).lerp(t);
+          final double extraPadding = Tween<double>(begin: 10.0, end: 24.0).transform(t);
           final double logoHeight = appBarHeight - 1.5 * extraPadding;
           return Padding(
             padding: EdgeInsets.only(
diff --git a/examples/flutter_gallery/lib/gallery/backdrop.dart b/examples/flutter_gallery/lib/gallery/backdrop.dart
index 92074f6..171d5c3 100644
--- a/examples/flutter_gallery/lib/gallery/backdrop.dart
+++ b/examples/flutter_gallery/lib/gallery/backdrop.dart
@@ -12,7 +12,7 @@
 const double _kBackAppBarHeight = 56.0; // back layer (options) appbar height
 
 // The size of the front layer heading's left and right beveled corners.
-final Tween<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween(
+final Animatable<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween(
   begin: const BorderRadius.only(
     topLeft: Radius.circular(12.0),
     topRight: Radius.circular(12.0),
@@ -199,6 +199,9 @@
   AnimationController _controller;
   Animation<double> _frontOpacity;
 
+  static final Animatable<double> _frontOpacityTween = Tween<double>(begin: 0.2, end: 1.0)
+    .chain(CurveTween(curve: const Interval(0.0, 0.4, curve: Curves.easeInOut)));
+
   @override
   void initState() {
     super.initState();
@@ -207,14 +210,7 @@
       value: 1.0,
       vsync: this,
     );
-
-    _frontOpacity =
-      Tween<double>(begin: 0.2, end: 1.0).animate(
-        CurvedAnimation(
-          parent: _controller,
-          curve: const Interval(0.0, 0.4, curve: Curves.easeInOut),
-        ),
-      );
+    _frontOpacity = _controller.drive(_frontOpacityTween);
   }
 
   @override
@@ -254,10 +250,10 @@
   }
 
   Widget _buildStack(BuildContext context, BoxConstraints constraints) {
-    final Animation<RelativeRect> frontRelativeRect = RelativeRectTween(
+    final Animation<RelativeRect> frontRelativeRect = _controller.drive(RelativeRectTween(
       begin: RelativeRect.fromLTRB(0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0),
       end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0),
-    ).animate(_controller);
+    ));
 
     final List<Widget> layers = <Widget>[
       // Back layer
@@ -301,7 +297,7 @@
               color: Theme.of(context).canvasColor,
               clipper: ShapeBorderClipper(
                 shape: BeveledRectangleBorder(
-                  borderRadius: _kFrontHeadingBevelRadius.lerp(_controller.value),
+                  borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value),
                 ),
               ),
               clipBehavior: Clip.antiAlias,
diff --git a/packages/flutter/lib/src/animation/animation.dart b/packages/flutter/lib/src/animation/animation.dart
index be2c9f6..65a951c 100644
--- a/packages/flutter/lib/src/animation/animation.dart
+++ b/packages/flutter/lib/src/animation/animation.dart
@@ -6,6 +6,8 @@
 
 import 'package:flutter/foundation.dart';
 
+import 'tween.dart';
+
 /// The status of an animation
 enum AnimationStatus {
   /// The animation is stopped at the beginning
@@ -38,6 +40,11 @@
 ///
 /// To create a new animation that you can run forward and backward, consider
 /// using [AnimationController].
+///
+/// See also:
+///
+///  * [Tween], which can be used to create [Animation] subclasses that
+///    convert `Animation<double>`s into other kinds of `Animation`s.
 abstract class Animation<T> extends Listenable implements ValueListenable<T> {
   /// Abstract const constructor. This constructor enables subclasses to provide
   /// const constructors so that they can be used in const expressions.
@@ -80,6 +87,76 @@
   /// Whether this animation is stopped at the end.
   bool get isCompleted => status == AnimationStatus.completed;
 
+  /// Chains a [Tween] (or [CurveTween]) to this [Animation].
+  ///
+  /// This method is only valid for `Animation<double>` instances (i.e. when `T`
+  /// is `double`). This means, for instance, that it can be called on
+  /// [AnimationController] objects, as well as [CurvedAnimation]s,
+  /// [ProxyAnimation]s, [ReverseAnimation]s, [TrainHoppingAnimation]s, etc.
+  ///
+  /// It returns an [Animation] specialized to the same type, `U`, as the
+  /// argument to the method (`child`), whose value is derived by applying the
+  /// given [Tween] to the value of this [Animation].
+  ///
+  /// ## Sample code
+  ///
+  /// Given an [AnimationController] `_controller`, the following code creates
+  /// an `Animation<Alignment>` that swings from top left to top right as the
+  /// controller goes from 0.0 to 1.0:
+  ///
+  /// ```dart
+  /// Animation<Alignment> _alignment1 = _controller.drive(
+  ///   AlignmentTween(
+  ///     begin: Alignment.topLeft,
+  ///     end: Alignment.topRight,
+  ///   ),
+  /// );
+  /// ```
+  ///
+  /// The `_alignment.value` could then be used in a widget's build method, for
+  /// instance, to position a child using an [Align] widget such that the
+  /// position of the child shifts over time from the top left to the top right.
+  ///
+  /// It is common to ease this kind of curve, e.g. making the transition slower
+  /// at the start and faster at the end. The following snippet shows one way to
+  /// chain the alignment tween in the previous example to an easing curve (in
+  /// this case, [Curves.easeIn]). In this example, the tween is created
+  /// elsewhere as a variable that can be reused, since none of its arguments
+  /// vary.
+  ///
+  /// ```dart
+  /// final Animatable<Alignment> _tween = AlignmentTween(begin: Alignment.topLeft, end: Alignment.topRight)
+  ///   .chain(CurveTween(curve: Curves.easeIn));
+  /// // ...
+  /// Animation<Alignment> _alignment2 = _controller.drive(_tween);
+  /// ```
+  ///
+  /// The following code is exactly equivalent, and is typically clearer when
+  /// the tweens are created inline, as might be preferred when the tweens have
+  /// values that depend on other variables:
+  ///
+  /// ```dart
+  /// Animation<Alignment> _alignment3 = _controller
+  ///   .drive(CurveTween(curve: Curves.easeIn))
+  ///   .drive(AlignmentTween(
+  ///     begin: Alignment.topLeft,
+  ///     end: Alignment.topRight,
+  ///   ));
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [Animatable.animate], which does the same thing.
+  ///  * [AnimationController], which is usually used to drive animations.
+  ///  * [CurvedAnimation], an alternative to [CurveTween] for applying easing
+  ///    curves, which supports distinct curves in the forward direction and the
+  ///    reverse direction.
+  @optionalTypeArgs
+  Animation<U> drive<U>(Animatable<U> child) {
+    assert(this is Animation<double>);
+    return child.animate(this as dynamic); // TODO(ianh): Clean this once https://github.com/dart-lang/sdk/issues/32120 is fixed.
+  }
+
   @override
   String toString() {
     return '${describeIdentity(this)}(${toStringDetails()})';
diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart
index a1cd6c3..f418065 100644
--- a/packages/flutter/lib/src/animation/animation_controller.dart
+++ b/packages/flutter/lib/src/animation/animation_controller.dart
@@ -17,7 +17,8 @@
 export 'package:flutter/scheduler.dart' show TickerFuture, TickerCanceled;
 
 // Examples can assume:
-// AnimationController _controller;
+// AnimationController _controller, fadeAnimationController, sizeAnimationController;
+// bool dismissed;
 
 /// The direction in which an animation is running.
 enum _AnimationDirection {
@@ -77,15 +78,39 @@
 /// a new value whenever the device running your app is ready to display a new
 /// frame (typically, this rate is around 60 values per second).
 ///
-/// An AnimationController needs a [TickerProvider], which is configured using
-/// the `vsync` argument on the constructor. If you are creating an
-/// AnimationController from a [State], then you can use the
-/// [TickerProviderStateMixin] and [SingleTickerProviderStateMixin] classes to
-/// obtain a suitable [TickerProvider]. The widget test framework [WidgetTester]
-/// object can be used as a ticker provider in the context of tests. In other
-/// contexts, you will have to either pass a [TickerProvider] from a higher
-/// level (e.g. indirectly from a [State] that mixes in
-/// [TickerProviderStateMixin]), or create a custom [TickerProvider] subclass.
+/// ## Ticker providers
+///
+/// An [AnimationController] needs a [TickerProvider], which is configured using
+/// the `vsync` argument on the constructor.
+///
+/// The [TickerProvider] interface describes a factory for [Ticker] objects. A
+/// [Ticker] is an object that knows how to register itself with the
+/// [SchedulerBinding] and fires a callback every frame. The
+/// [AnimationController] class uses a [Ticker] to step through the animation
+/// that it controls.
+///
+/// If an [AnimationController] is being created from a [State], then the State
+/// can use the [TickerProviderStateMixin] and [SingleTickerProviderStateMixin]
+/// classes to implement the [TickerProvider] interface. The
+/// [TickerProviderStateMixin] class always works for this purpose; the
+/// [SingleTickerProviderStateMixin] is slightly more efficient in the case of
+/// the class only ever needing one [Ticker] (e.g. if the class creates only a
+/// single [AnimationController] during its entire lifetime).
+///
+/// The widget test framework [WidgetTester] object can be used as a ticker
+/// provider in the context of tests. In other contexts, you will have to either
+/// pass a [TickerProvider] from a higher level (e.g. indirectly from a [State]
+/// that mixes in [TickerProviderStateMixin]), or create a custom
+/// [TickerProvider] subclass.
+///
+/// ## Life cycle
+///
+/// An [AnimationController] should be [dispose]d when it is no longer needed.
+/// This reduces the likelihood of leaks. When used with a [StatefulWidget], it
+/// is common for an [AnimationController] to be created in the
+/// [State.initState] method and then disposed in the [State.dispose] method.
+///
+/// ## Using [Future]s with [AnimationController]
 ///
 /// The methods that start animations return a [TickerFuture] object which
 /// completes when the animation completes successfully, and never throws an
@@ -94,7 +119,61 @@
 /// completes when the animation completes successfully, and completes with an
 /// error when the animation is aborted.
 ///
-/// This can be used to write code such as:
+/// This can be used to write code such as the `fadeOutAndUpdateState` method
+/// below.
+///
+/// ## Sample code
+///
+/// Here is a stateful [Foo] widget. Its [State] uses the
+/// [SingleTickerProviderStateMixin] to implement the necessary
+/// [TickerProvider], creating its controller in the [initState] method and
+/// disposing of it in the [dispose] method. The duration of the controller is
+/// configured from a property in the [Foo] widget; as that changes, the
+/// [didUpdateWidget] method is used to update the controller.
+///
+/// ```dart
+/// class Foo extends StatefulWidget {
+///   Foo({ Key key, this.duration }) : super(key: key);
+///
+///   final Duration duration;
+///
+///   @override
+///   _FooState createState() => _FooState();
+/// }
+///
+/// class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
+///   AnimationController _controller;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _controller = AnimationController(
+///       vsync: this, // the SingleTickerProviderStateMixin
+///       duration: widget.duration,
+///     );
+///   }
+///
+///   @override
+///   void didUpdateWidget(Foo oldWidget) {
+///     super.didUpdateWidget(oldWidget);
+///     _controller.duration = widget.duration;
+///   }
+///
+///   @override
+///   void dispose() {
+///     _controller.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(); // ...
+///   }
+/// }
+/// ```
+///
+/// The following method (for a [State] subclass) drives two animation
+/// controllers using Dart's asynchronous syntax for awaiting [Future] objects:
 ///
 /// ```dart
 /// Future<Null> fadeOutAndUpdateState() async {
@@ -110,14 +189,20 @@
 /// }
 /// ```
 ///
-/// ...which asynchronously runs one animation, then runs another, then changes
-/// the state of the widget, without having to verify [State.mounted] is still
-/// true at each step, and without having to chain futures together explicitly.
-/// (This assumes that the controllers are created in [State.initState] and
-/// disposed in [State.dispose].)
+/// The assumption in the code above is that the animation controllers are being
+/// disposed in the [State] subclass' override of the [State.dispose] method.
+/// Since disposing the controller cancels the animation (raising a
+/// [TickerCanceled] exception), the code here can skip verifying whether
+/// [State.mounted] is still true at each step. (Again, this assumes that the
+/// controllers are created in [State.initState] and disposed in
+/// [State.dispose], as described in the previous section.)
+///
+/// See also:
+///
+///  * [Tween], the base class for converting an [AnimationController] to a
+///    range of values of other types.
 class AnimationController extends Animation<double>
   with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
-
   /// Creates an animation controller.
   ///
   /// * [value] is the initial value of the animation. If defaults to the lower
diff --git a/packages/flutter/lib/src/animation/animations.dart b/packages/flutter/lib/src/animation/animations.dart
index d0d6672..a6c1c79 100644
--- a/packages/flutter/lib/src/animation/animations.dart
+++ b/packages/flutter/lib/src/animation/animations.dart
@@ -248,6 +248,13 @@
 /// Using a [ReverseAnimation] is different from simply using a [Tween] with a
 /// begin of 1.0 and an end of 0.0 because the tween does not change the status
 /// or direction of the animation.
+///
+/// See also:
+///
+///  * [Curve.flipped] and [FlippedCurve], which provide a similar effect but on
+///    [Curve]s.
+///  * [CurvedAnimation], which can take separate curves for when the animation
+///    is going forward than for when it is going in reverse.
 class ReverseAnimation extends Animation<double>
   with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
 
@@ -312,15 +319,8 @@
 /// An animation that applies a curve to another animation.
 ///
 /// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to
-/// an animation object wrapped in the [CurvedAnimation].
-///
-/// For example, the following code snippet shows how you can apply a curve to a
-/// linear animation produced by an [AnimationController]:
-///
-/// ``` dart
-/// final AnimationController controller = AnimationController(duration: const Duration(milliseconds: 500));
-/// final CurvedAnimation animation = CurvedAnimation(parent: controller, curve: Curves.ease);
-///```
+/// an animation object, especially if you want different curves when the
+/// animation is going forward vs when it is going backward.
 ///
 /// Depending on the given curve, the output of the [CurvedAnimation] could have
 /// a wider range than its input. For example, elastic curves such as
@@ -328,6 +328,42 @@
 /// range of 0.0 to 1.0.
 ///
 /// If you want to apply a [Curve] to a [Tween], consider using [CurveTween].
+///
+/// ## Sample code
+///
+/// The following code snippet shows how you can apply a curve to a linear
+/// animation produced by an [AnimationController] `controller`.
+///
+/// ```dart
+/// final Animation<double> animation = CurvedAnimation(
+///   parent: controller,
+///   curve: Curves.ease,
+/// );
+/// ```
+///
+/// This second code snippet shows how to apply a different curve in the forward
+/// direction than in the reverse direction. This can't be done using a
+/// [CurveTween] (since [Tween]s are not aware of the animation direction when
+/// they are applied).
+///
+/// ```dart
+/// final Animation<double> animation = CurvedAnimation(
+///   parent: controller,
+///   curve: Curves.easeIn,
+///   reverseCurve: Curves.easeOut,
+/// );
+/// ```
+///
+/// By default, the [reverseCurve] matches the forward [curve].
+///
+/// See also:
+///
+///  * [CurveTween], for an alternative way of expressing the first sample
+///    above.
+///  * [AnimationController], for examples of creating and disposing of an
+///    [AnimationController].
+///  * [Curve.flipped] and [FlippedCurve], which provide the reverse of a
+///    [Curve].
 class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
   /// Creates a curved animation.
   ///
@@ -428,11 +464,19 @@
 
 enum _TrainHoppingMode { minimize, maximize }
 
-/// This animation starts by proxying one animation, but can be given a
-/// second animation. When their times cross (either because the second is
+/// This animation starts by proxying one animation, but when the value of that
+/// animation crosses the value of the second (either because the second is
 /// going in the opposite direction, or because the one overtakes the other),
-/// the animation hops over to proxying the second animation, and the second
-/// animation becomes the new "first" performance.
+/// the animation hops over to proxying the second animation.
+///
+/// When the [TrainHoppingAnimation] starts proxying the second animation
+/// instead of the first, the [onSwitchedTrain] callback is called.
+///
+/// If the two animations start at the same value, then the
+/// [TrainHoppingAnimation] immediately hops to the second animation, and the
+/// [onSwitchedTrain] callback is not called. If only one animation is provided
+/// (i.e. if the second is null), then the [TrainHoppingAnimation] just proxies
+/// the first animation.
 ///
 /// Since this object must track the two animations even when it has no
 /// listeners of its own, instead of shutting down when all its listeners are
@@ -444,33 +488,41 @@
   /// Creates a train-hopping animation.
   ///
   /// The current train argument must not be null but the next train argument
-  /// can be null.
+  /// can be null. If the next train is null, then this object will just proxy
+  /// the first animation and never hop.
   TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain })
     : assert(_currentTrain != null) {
     if (_nextTrain != null) {
-      if (_currentTrain.value > _nextTrain.value) {
+      if (_currentTrain.value == _nextTrain.value) {
+        _currentTrain = _nextTrain;
+        _nextTrain = null;
+      } else if (_currentTrain.value > _nextTrain.value) {
         _mode = _TrainHoppingMode.maximize;
       } else {
+        assert(_currentTrain.value < _nextTrain.value);
         _mode = _TrainHoppingMode.minimize;
-        if (_currentTrain.value == _nextTrain.value) {
-          _currentTrain = _nextTrain;
-          _nextTrain = null;
-        }
       }
     }
     _currentTrain.addStatusListener(_statusChangeHandler);
     _currentTrain.addListener(_valueChangeHandler);
     _nextTrain?.addListener(_valueChangeHandler);
-    assert(_mode != null);
+    assert(_mode != null || _nextTrain == null);
   }
 
-  /// The animation that is current driving this animation.
+  /// The animation that is currently driving this animation.
+  ///
+  /// The identity of this object will change from the first animation to the
+  /// second animation when [onSwitchedTrain] is called.
   Animation<double> get currentTrain => _currentTrain;
   Animation<double> _currentTrain;
   Animation<double> _nextTrain;
   _TrainHoppingMode _mode;
 
-  /// Called when this animation switches to be driven by a different animation.
+  /// Called when this animation switches to be driven by the second animation.
+  ///
+  /// This is not called if the two animations provided to the constructor have
+  /// the same value at the time of the call to the constructor. In that case,
+  /// the second animation is used from the start, and the first is ignored.
   VoidCallback onSwitchedTrain;
 
   AnimationStatus _lastStatus;
@@ -491,6 +543,7 @@
     assert(_currentTrain != null);
     bool hop = false;
     if (_nextTrain != null) {
+      assert(_mode != null);
       switch (_mode) {
         case _TrainHoppingMode.minimize:
           hop = _nextTrain.value <= _currentTrain.value;
diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart
index 9e270ff..f1596c5 100644
--- a/packages/flutter/lib/src/animation/curves.dart
+++ b/packages/flutter/lib/src/animation/curves.dart
@@ -6,11 +6,22 @@
 
 import 'package:flutter/foundation.dart';
 
-/// A mapping of the unit interval to the unit interval.
+/// An easing curve, i.e. a mapping of the unit interval to the unit interval.
+///
+/// Easing curves are used to adjust the rate of change of an animation over
+/// time, allowing them to speed up and slow down, rather than moving at a
+/// constant rate.
 ///
 /// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
 ///
-/// See [Curves] for a collection of common animation curves.
+/// See also:
+///
+///  * [Curves], a collection of common animation easing curves.
+///  * [CurveTween], which can be used to apply a [Curve] to an [Animation].
+///  * [Canvas.drawArc], which draws an arc, and has nothing to do with easing
+///    curves.
+///  * [Animatable], for a more flexible interface that maps fractions to
+///    arbitrary values.
 @immutable
 abstract class Curve {
   /// Abstract const constructor. This constructor enables subclasses to provide
@@ -26,7 +37,8 @@
   double transform(double t);
 
   /// Returns a new curve that is the reversed inversion of this one.
-  /// This is often useful as the reverseCurve of an [Animation].
+  ///
+  /// This is often useful with [CurvedAnimation.reverseCurve].
   ///
   /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
   /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
@@ -34,6 +46,8 @@
   /// See also:
   ///
   ///  * [FlippedCurve], the class that is used to implement this getter.
+  ///  * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
+  ///  * [CurvedAnimation], which can take a separate curve and reverse curve.
   Curve get flipped => FlippedCurve(this);
 
   @override
@@ -248,13 +262,21 @@
 /// A curve that is the reversed inversion of its given curve.
 ///
 /// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t
-/// increases from 0.0 to 1.0) and returns the inverse of the given curve's value
-/// (i.e., 1.0 minus the given curve's value).
+/// increases from 0.0 to 1.0) and returns the inverse of the given curve's
+/// value (i.e., 1.0 minus the given curve's value).
 ///
 /// This is the class used to implement the [flipped] getter on curves.
 ///
+/// This is often useful with [CurvedAnimation.reverseCurve].
+///
 /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
-/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped_curve.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
+///
+/// See also:
+///
+///  * [Curve.flipped], which provides the [FlippedCurve] of a [Curve].
+///  * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
+///  * [CurvedAnimation], which can take a separate curve and reverse curve.
 class FlippedCurve extends Curve {
   /// Creates a flipped curve.
   ///
diff --git a/packages/flutter/lib/src/animation/tween.dart b/packages/flutter/lib/src/animation/tween.dart
index 23f4100..ee21292 100644
--- a/packages/flutter/lib/src/animation/tween.dart
+++ b/packages/flutter/lib/src/animation/tween.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:ui' show Color, Size, Rect, hashValues;
+import 'dart:ui' show Color, Size, Rect;
 
 import 'package:flutter/foundation.dart';
 
@@ -10,27 +10,65 @@
 import 'animations.dart';
 import 'curves.dart';
 
+// Examples can assume:
+// Animation<Offset> _animation;
+
 /// An object that can produce a value of type `T` given an [Animation<double>]
 /// as input.
 ///
 /// Typically, the values of the input animation are nominally in the range 0.0
 /// to 1.0. In principle, however, any value could be provided.
+///
+/// The main subclass of [Animatable] is [Tween].
 abstract class Animatable<T> {
   /// Abstract const constructor. This constructor enables subclasses to provide
   /// const constructors so that they can be used in const expressions.
   const Animatable();
 
-  /// The current value of this object for the given animation.
-  T evaluate(Animation<double> animation);
+  /// Returns the value of the object at point `t`.
+  ///
+  /// The value of `t` is nominally a fraction in the range 0.0 to 1.0, though
+  /// in practice it may extend outside this range.
+  ///
+  /// See also:
+  ///
+  ///  * [evaluate], which is a shorthand for applying [transform] to the value
+  ///    of an [Animation].
+  ///  * [Curve.transform], a similar method for easing curves.
+  T transform(double t);
 
-  /// Returns a new Animation that is driven by the given animation but that
+  /// The current value of this object for the given [Animation].
+  ///
+  /// This function is implemented by deferring to [transform]. Subclasses that
+  /// want to provide custom behavior should override [transform], not
+  /// [evaluate].
+  ///
+  /// See also:
+  ///
+  ///  * [transform], which is similar but takes a `t` value directly instead of
+  ///    an [Animation].
+  ///  * [animate], which creates an [Animation] out of this object, continually
+  ///    applying [evaluate].
+  T evaluate(Animation<double> animation) => transform(animation.value);
+
+  /// Returns a new [Animation] that is driven by the given animation but that
   /// takes on values determined by this object.
+  ///
+  /// Essentially this returns an [Animation] that automatically applies the
+  /// [evaluate] method to the parent's value.
+  ///
+  /// See also:
+  ///
+  ///  * [AnimationController.drive], which does the same thing from the
+  ///    opposite starting point.
   Animation<T> animate(Animation<double> parent) {
     return _AnimatedEvaluation<T>(parent, this);
   }
 
-  /// Returns a new Animatable whose value is determined by first evaluating
+  /// Returns a new [Animatable] whose value is determined by first evaluating
   /// the given parent and then evaluating this object.
+  ///
+  /// This allows [Tween]s to be chained before obtaining an [Animation].
   Animatable<T> chain(Animatable<double> parent) {
     return _ChainedEvaluation<T>(parent, this);
   }
@@ -65,9 +103,8 @@
   final Animatable<T> _evaluatable;
 
   @override
-  T evaluate(Animation<double> animation) {
-    final double value = _parent.evaluate(animation);
-    return _evaluatable.evaluate(AlwaysStoppedAnimation<double>(value));
+  T transform(double t) {
+    return _evaluatable.transform(_parent.transform(t));
   }
 
   @override
@@ -87,27 +124,85 @@
 /// You can chain [Tween] objects together using the [chain] method, so that a
 /// single [Animation] object is configured by multiple [Tween] objects called
 /// in succession. This is different than calling the [animate] method twice,
-/// which results in two [Animation] separate objects, each configured with a
+/// which results in two separate [Animation] objects, each configured with a
 /// single [Tween].
 ///
 /// ## Sample code
 ///
 /// Suppose `_controller` is an [AnimationController], and we want to create an
 /// [Animation<Offset>] that is controlled by that controller, and save it in
-/// `_animation`:
+/// `_animation`. Here are two possible ways of expressing this:
 ///
 /// ```dart
-/// Animation<Offset> _animation = Tween<Offset>(
+/// _animation = _controller.drive(
+///   Tween<Offset>(
+///     begin: const Offset(100.0, 50.0),
+///     end: const Offset(200.0, 300.0),
+///   ),
+/// );
+/// ```
+///
+/// ```dart
+/// _animation = Tween<Offset>(
 ///   begin: const Offset(100.0, 50.0),
 ///   end: const Offset(200.0, 300.0),
 /// ).animate(_controller);
 /// ```
 ///
-/// That would provide an `_animation` that, over the lifetime of the
-/// `_controller`'s animation, returns a value that depicts a point along the
-/// line between the two offsets above. If we used a [MaterialPointArcTween]
-/// instead of a [Tween<Offset>] in the code above, the points would follow a
-/// pleasing curve instead of a straight line, with no other changes necessary.
+/// In both cases, the `_animation` variable holds an object that, over the
+/// lifetime of the `_controller`'s animation, returns a value
+/// (`_animation.value`) that depicts a point along the line between the two
+/// offsets above. If we used a [MaterialPointArcTween] instead of a
+/// [Tween<Offset>] in the code above, the points would follow a pleasing curve
+/// instead of a straight line, with no other changes necessary.
+///
+/// ## Performance optimizations
+///
+/// Tweens are mutable; specifically, their [begin] and [end] values can be
+/// changed at runtime. An object created with [Animation.drive] using a [Tween]
+/// will immediately honor changes to that underlying [Tween] (though the
+/// listeners will only be triggered if the [Animation] is actively animating).
+/// This can be used to change an animation on the fly without having to
+/// recreate all the objects in the chain from the [AnimationController] to the
+/// final [Tween].
+///
+/// If a [Tween]'s values are never changed, however, a further optimisation can
+/// be applied: the object can be stored in a `static final` variable, so that
+/// the exact same instance is used whenever the [Tween] is needed. This is
+/// preferable to creating an identical [Tween] afresh each time a [State.build]
+/// method is called, for example.
+///
+/// ## Types with special considerations
+///
+/// Classes with [lerp] static methods typically have corresponding dedicated
+/// [Tween] subclasses that call that method. For example, [ColorTween] uses
+/// [Color.lerp] to implement the [ColorTween.lerp] method.
+///
+/// Types that define `+` and `-` operators to combine values (`T + T → T` and
+/// `T - T → T`) and an `*` operator to scale by multiplying with a double (`T *
+/// double → T`) can be directly used with `Tween<T>`.
+///
+/// This does not extend to any type with `+`, `-`, and `*` operators. In
+/// particular, [int] does not satisfy this precise contract (`int * double`
+/// actually returns [num], not [int]). There are therefore two specific classes
+/// that can be used to interpolate integers:
+///
+///  * [IntTween], which is an approximation of a linear interpolation (using
+///    [double.round]).
+///  * [StepTween], which uses [double.floor] to ensure that the result is
+///    never greater than it would be using if a `Tween<double>`.
+///
+/// The relevant operators on [Size] also don't fulfill this contract, so
+/// [SizeTween] uses [Size.lerp].
+///
+/// In addition, some of the types that _do_ have suitable `+`, `-`, and `*`
+/// operators still have dedicated [Tween] subclasses that perform the
+/// interpolation in a more specialized manner. One such class is
+/// [MaterialPointArcTween], which is mentioned above. The [AlignmentTween], and
+/// [AlignmentGeometryTween], and [FractionalOffsetTween] are another group of
+/// [Tween]s that use dedicated `lerp` methods instead of merely relying on the
+/// operators (in particular, this allows them to handle null values in a more
+/// useful manner).
 class Tween<T extends dynamic> extends Animatable<T> {
   /// Creates a tween.
   ///
@@ -133,6 +228,7 @@
   /// The default implementation of this method uses the [+], [-], and [*]
   /// operators on `T`. The [begin] and [end] properties must therefore be
   /// non-null by the time this method is called.
+  @protected
   T lerp(double t) {
     assert(begin != null);
     assert(end != null);
@@ -144,15 +240,15 @@
   /// This method returns `begin` and `end` when the animation values are 0.0 or
   /// 1.0, respectively.
   ///
-  /// This function is implemented by deferring to [lerp]. Subclasses that want to
-  /// provide custom behavior should override [lerp], not [evaluate].
+  /// This function is implemented by deferring to [lerp]. Subclasses that want
+  /// to provide custom behavior should override [lerp], not [transform] (nor
+  /// [evaluate]).
   ///
   /// See the constructor for details about whether the [begin] and [end]
   /// properties may be null when this is called. It varies from subclass to
   /// subclass.
   @override
-  T evaluate(Animation<double> animation) {
-    final double t = animation.value;
+  T transform(double t) {
     if (t == 0.0)
       return begin;
     if (t == 1.0)
@@ -161,20 +257,6 @@
   }
 
   @override
-  bool operator ==(dynamic other) {
-    if (identical(this, other))
-      return true;
-    if (other.runtimeType != runtimeType)
-      return false;
-    final Tween<T> typedOther = other;
-    return begin == typedOther.begin
-        && end == typedOther.end;
-  }
-
-  @override
-  int get hashCode => hashValues(begin, end);
-
-  @override
   String toString() => '$runtimeType($begin \u2192 $end)';
 }
 
@@ -282,7 +364,7 @@
 ///
 /// This class specializes the interpolation of [Tween<int>] to be
 /// appropriate for integers by interpolating between the given begin
-/// and end values and then using [int.floor] to return the current
+/// and end values and then using [double.floor] to return the current
 /// integer component, dropping the fractional component.
 ///
 /// This results in a value that is never greater than the equivalent
@@ -321,6 +403,26 @@
 /// This class differs from [CurvedAnimation] in that [CurvedAnimation] applies
 /// a curve to an existing [Animation] object whereas [CurveTween] can be
 /// chained with another [Tween] prior to receiving the underlying [Animation].
+/// ([CurvedAnimation] also has the additional ability of having different
+/// curves when the animation is going forward vs when it is going backward,
+/// which can be useful in some scenarios.)
+///
+/// ## Sample code
+///
+/// The following code snippet shows how you can apply a curve to a linear
+/// animation produced by an [AnimationController] `controller`:
+///
+/// ```dart
+/// final Animation<double> animation = controller.drive(
+///   CurveTween(curve: Curves.ease),
+/// );
+/// ```
+///
+/// See also:
+///
+///  * [CurvedAnimation], for an alternative way of expressing the sample above.
+///  * [AnimationController], for examples of creating and disposing of an
+///    [AnimationController].
 class CurveTween extends Animatable<double> {
   /// Creates a curve tween.
   ///
@@ -332,8 +434,7 @@
   Curve curve;
 
   @override
-  double evaluate(Animation<double> animation) {
-    final double t = animation.value;
+  double transform(double t) {
     if (t == 0.0 || t == 1.0) {
       assert(curve.transform(t).round() == t);
       return t;
diff --git a/packages/flutter/lib/src/animation/tween_sequence.dart b/packages/flutter/lib/src/animation/tween_sequence.dart
index cb2b1de..1ff8861 100644
--- a/packages/flutter/lib/src/animation/tween_sequence.dart
+++ b/packages/flutter/lib/src/animation/tween_sequence.dart
@@ -1,7 +1,6 @@
 import 'package:flutter/foundation.dart';
 
 import 'animation.dart';
-import 'animations.dart';
 import 'tween.dart';
 
 /// Enables creating an [Animation] whose value is defined by a
@@ -67,12 +66,11 @@
   T _evaluateAt(double t, int index) {
     final TweenSequenceItem<T> element = _items[index];
     final double tInterval = _intervals[index].value(t);
-    return element.tween.evaluate(AlwaysStoppedAnimation<double>(tInterval));
+    return element.tween.transform(tInterval);
   }
 
   @override
-  T evaluate(Animation<double> animation) {
-    final double t = animation.value;
+  T transform(double t) {
     assert(t >= 0.0 && t <= 1.0);
     if (t == 1.0)
       return _evaluateAt(t, _items.length - 1);
@@ -100,6 +98,8 @@
   /// animation's duration indicated by [weight] and this item's position
   /// in the list of items.
   ///
+  /// ## Sample code
+  ///
   /// The value of this item can be "curved" by chaining it to a [CurveTween].
   /// For example to create a tween that eases from 0.0 to 10.0:
   ///
diff --git a/packages/flutter/lib/src/cupertino/button.dart b/packages/flutter/lib/src/cupertino/button.dart
index 817b6f0..046cc11 100644
--- a/packages/flutter/lib/src/cupertino/button.dart
+++ b/packages/flutter/lib/src/cupertino/button.dart
@@ -125,16 +125,10 @@
   // Eyeballed values. Feel free to tweak.
   static const Duration kFadeOutDuration = Duration(milliseconds: 10);
   static const Duration kFadeInDuration = Duration(milliseconds: 100);
-  Tween<double> _opacityTween;
+  final Tween<double> _opacityTween = Tween<double>(begin: 1.0);
 
   AnimationController _animationController;
-
-  void _setTween() {
-    _opacityTween = Tween<double>(
-      begin: 1.0,
-      end: widget.pressedOpacity ?? 1.0,
-    );
-  }
+  Animation<double> _opacityAnimation;
 
   @override
   void initState() {
@@ -144,22 +138,29 @@
       value: 0.0,
       vsync: this,
     );
+    _opacityAnimation = _animationController
+      .drive(CurveTween(curve: Curves.decelerate))
+      .drive(_opacityTween);
     _setTween();
   }
 
   @override
+  void didUpdateWidget(CupertinoButton old) {
+    super.didUpdateWidget(old);
+    _setTween();
+  }
+
+  void _setTween() {
+    _opacityTween.end = widget.pressedOpacity ?? 1.0;
+  }
+
+  @override
   void dispose() {
     _animationController.dispose();
     _animationController = null;
     super.dispose();
   }
 
-  @override
-  void didUpdateWidget(CupertinoButton old) {
-    super.didUpdateWidget(old);
-    _setTween();
-  }
-
   bool _buttonHeldDown = false;
 
   void _handleTapDown(TapDownDetails event) {
@@ -217,10 +218,7 @@
               minHeight: widget.minSize,
             ),
           child: FadeTransition(
-            opacity: _opacityTween.animate(CurvedAnimation(
-              parent: _animationController,
-              curve: Curves.decelerate,
-            )),
+            opacity: _opacityAnimation,
             child: DecoratedBox(
               decoration: BoxDecoration(
                 borderRadius: widget.borderRadius,
diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart
index 104df40..594cbbe 100644
--- a/packages/flutter/lib/src/cupertino/nav_bar.dart
+++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -1520,11 +1520,11 @@
            // paintBounds are based on offset zero so it's ok to expand the Rects.
            bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds);
 
-  static final Tween<double> fadeOut = Tween<double>(
+  static final Animatable<double> fadeOut = Tween<double>(
     begin: 1.0,
     end: 0.0,
   );
-  static final Tween<double> fadeIn = Tween<double>(
+  static final Animatable<double> fadeIn = Tween<double>(
     begin: 0.0,
     end: 1.0,
   );
@@ -1601,15 +1601,15 @@
   }
 
   Animation<double> fadeInFrom(double t, { Curve curve = Curves.easeIn }) {
-    return fadeIn.animate(
-      CurvedAnimation(curve: Interval(t, 1.0, curve: curve), parent: animation),
-    );
+    return animation.drive(fadeIn.chain(
+      CurveTween(curve: Interval(t, 1.0, curve: curve)),
+    ));
   }
 
   Animation<double> fadeOutBy(double t, { Curve curve = Curves.easeOut }) {
-    return fadeOut.animate(
-      CurvedAnimation(curve: Interval(0.0, t, curve: curve), parent: animation),
-    );
+    return animation.drive(fadeOut.chain(
+      CurveTween(curve: Interval(0.0, t, curve: curve)),
+    ));
   }
 
   Widget get bottomLeading {
@@ -1663,7 +1663,7 @@
     );
 
     return PositionedTransition(
-      rect: positionTween.animate(animation),
+      rect: animation.drive(positionTween),
       child: FadeTransition(
         opacity: fadeOutBy(0.2),
         child: DefaultTextStyle(
@@ -1687,12 +1687,12 @@
 
     if (bottomMiddle != null && topBackLabel != null) {
       return PositionedTransition(
-        rect: slideFromLeadingEdge(
+        rect: animation.drive(slideFromLeadingEdge(
           fromKey: bottomComponents.middleKey,
           fromNavBarBox: bottomNavBarBox,
           toKey: topComponents.backLabelKey,
           toNavBarBox: topNavBarBox,
-        ).animate(animation),
+        )),
         child: FadeTransition(
           // A custom middle widget like a segmented control fades away faster.
           opacity: fadeOutBy(bottomHasUserMiddle ? 0.4 : 0.7),
@@ -1701,10 +1701,10 @@
             // edge of a constantly sized outer box.
             alignment: AlignmentDirectional.centerStart,
             child: DefaultTextStyleTransition(
-              style: TextStyleTween(
+              style: animation.drive(TextStyleTween(
                 begin: _kMiddleTitleTextStyle,
                 end: topActionsStyle,
-              ).animate(animation),
+              )),
               child: bottomMiddle.child,
             ),
           ),
@@ -1742,12 +1742,12 @@
 
     if (bottomLargeTitle != null && topBackLabel != null) {
       return PositionedTransition(
-        rect: slideFromLeadingEdge(
+        rect: animation.drive(slideFromLeadingEdge(
           fromKey: bottomComponents.largeTitleKey,
           fromNavBarBox: bottomNavBarBox,
           toKey: topComponents.backLabelKey,
           toNavBarBox: topNavBarBox,
-        ).animate(animation),
+        )),
         child: FadeTransition(
           opacity: fadeOutBy(0.6),
           child: Align(
@@ -1755,10 +1755,10 @@
             // edge of a constantly sized outer box.
             alignment: AlignmentDirectional.centerStart,
             child: DefaultTextStyleTransition(
-              style: TextStyleTween(
+              style: animation.drive(TextStyleTween(
                 begin: _kLargeTitleTextStyle,
                 end: topActionsStyle,
-              ).animate(animation),
+              )),
               maxLines: 1,
               overflow: TextOverflow.ellipsis,
               child: bottomLargeTitle.child,
@@ -1779,7 +1779,7 @@
       // Just shift slightly towards the right instead of moving to the back
       // label position.
       return PositionedTransition(
-        rect: positionTween.animate(animation),
+        rect: animation.drive(positionTween),
         child: FadeTransition(
           opacity: fadeOutBy(0.4),
           // Keep the font when transitioning into a non-back-label leading.
@@ -1850,7 +1850,7 @@
     );
 
     return PositionedTransition(
-      rect: positionTween.animate(animation),
+      rect: animation.drive(positionTween),
       child: FadeTransition(
         opacity: fadeInFrom(bottomBackChevron == null ? 0.7 : 0.4),
         child: DefaultTextStyle(
@@ -1877,10 +1877,10 @@
 
     Animation<double> midClickOpacity;
     if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0) {
-      midClickOpacity = Tween<double>(
+      midClickOpacity = animation.drive(Tween<double>(
         begin: 0.0,
         end: topBackLabelOpacity.opacity.value,
-      ).animate(animation);
+      ));
     }
 
     // Pick up from an incoming transition from the large title. This is
@@ -1893,19 +1893,19 @@
         bottomLargeExpanded
     ) {
       return PositionedTransition(
-        rect: slideFromLeadingEdge(
+        rect: animation.drive(slideFromLeadingEdge(
           fromKey: bottomComponents.largeTitleKey,
           fromNavBarBox: bottomNavBarBox,
           toKey: topComponents.backLabelKey,
           toNavBarBox: topNavBarBox,
-        ).animate(animation),
+        )),
         child: FadeTransition(
           opacity: midClickOpacity ?? fadeInFrom(0.4),
           child: DefaultTextStyleTransition(
-            style: TextStyleTween(
+            style: animation.drive(TextStyleTween(
               begin: _kLargeTitleTextStyle,
               end: topActionsStyle,
-            ).animate(animation),
+            )),
             maxLines: 1,
             overflow: TextOverflow.ellipsis,
             child: topBackLabel.child,
@@ -1918,19 +1918,19 @@
     // and expanded instead of middle.
     if (bottomMiddle != null && topBackLabel != null) {
       return PositionedTransition(
-        rect: slideFromLeadingEdge(
+        rect: animation.drive(slideFromLeadingEdge(
           fromKey: bottomComponents.middleKey,
           fromNavBarBox: bottomNavBarBox,
           toKey: topComponents.backLabelKey,
           toNavBarBox: topNavBarBox,
-        ).animate(animation),
+        )),
         child: FadeTransition(
           opacity: midClickOpacity ?? fadeInFrom(0.3),
           child: DefaultTextStyleTransition(
-            style: TextStyleTween(
+            style: animation.drive(TextStyleTween(
               begin: _kMiddleTitleTextStyle,
               end: topActionsStyle,
-            ).animate(animation),
+            )),
             child: topBackLabel.child,
           ),
         ),
@@ -1962,7 +1962,7 @@
     );
 
     return PositionedTransition(
-      rect: positionTween.animate(animation),
+      rect: animation.drive(positionTween),
       child: FadeTransition(
         opacity: fadeInFrom(0.25),
         child: DefaultTextStyle(
@@ -2005,7 +2005,7 @@
     );
 
     return PositionedTransition(
-      rect: positionTween.animate(animation),
+      rect: animation.drive(positionTween),
       child: FadeTransition(
         opacity: fadeInFrom(0.3),
         child: DefaultTextStyle(
diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart
index 2f33cb5..fc7103d 100644
--- a/packages/flutter/lib/src/cupertino/route.dart
+++ b/packages/flutter/lib/src/cupertino/route.dart
@@ -19,19 +19,19 @@
 const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
 
 // Offset from offscreen to the right to fully on screen.
-final Tween<Offset> _kRightMiddleTween = Tween<Offset>(
+final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
   begin: const Offset(1.0, 0.0),
   end: Offset.zero,
 );
 
 // Offset from fully on screen to 1/3 offscreen to the left.
-final Tween<Offset> _kMiddleLeftTween = Tween<Offset>(
+final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(
   begin: Offset.zero,
   end: const Offset(-1.0/3.0, 0.0),
 );
 
 // Offset from offscreen below to fully on screen.
-final Tween<Offset> _kBottomUpTween = Tween<Offset>(
+final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
   begin: const Offset(0.0, 1.0),
   end: Offset.zero,
 );
@@ -354,28 +354,22 @@
     @required this.child,
     @required bool linearTransition,
   }) : assert(linearTransition != null),
-       _primaryPositionAnimation = linearTransition
-         ? _kRightMiddleTween.animate(primaryRouteAnimation)
-         : _kRightMiddleTween.animate(
-             CurvedAnimation(
-               parent: primaryRouteAnimation,
-               curve: Curves.easeOut,
-               reverseCurve: Curves.easeIn,
-             )
-           ),
-       _secondaryPositionAnimation = _kMiddleLeftTween.animate(
-         CurvedAnimation(
-           parent: secondaryRouteAnimation,
-           curve: Curves.easeOut,
-           reverseCurve: Curves.easeIn,
-         )
-       ),
-       _primaryShadowAnimation = _kGradientShadowTween.animate(
+       _primaryPositionAnimation = (linearTransition ? primaryRouteAnimation :
          CurvedAnimation(
            parent: primaryRouteAnimation,
            curve: Curves.easeOut,
+           reverseCurve: Curves.easeIn,
          )
-       ),
+       ).drive(_kRightMiddleTween),
+       _secondaryPositionAnimation = CurvedAnimation(
+         parent: secondaryRouteAnimation,
+         curve: Curves.easeOut,
+         reverseCurve: Curves.easeIn,
+       ).drive(_kMiddleLeftTween),
+       _primaryShadowAnimation = CurvedAnimation(
+         parent: primaryRouteAnimation,
+         curve: Curves.easeOut,
+       ).drive(_kGradientShadowTween),
        super(key: key);
 
   // When this page is coming in to cover another page.
@@ -418,12 +412,9 @@
     Key key,
     @required Animation<double> animation,
     @required this.child,
-  }) : _positionAnimation = _kBottomUpTween.animate(
-         CurvedAnimation(
-           parent: animation,
-           curve: Curves.easeInOut,
-         )
-       ),
+  }) : _positionAnimation = animation
+         .drive(CurveTween(curve: Curves.easeInOut))
+         .drive(_kBottomUpTween),
        super(key: key);
 
   final Animation<Offset> _positionAnimation;
@@ -859,6 +850,9 @@
   );
 }
 
+final Animatable<double> _dialogTween = Tween<double>(begin: 1.2, end: 1.0)
+  .chain(CurveTween(curve: Curves.fastOutSlowIn));
+
 Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
   final CurvedAnimation fadeAnimation = CurvedAnimation(
     parent: animation,
@@ -874,15 +868,7 @@
     opacity: fadeAnimation,
     child: ScaleTransition(
       child: child,
-      scale: Tween<double>(
-        begin: 1.2,
-        end: 1.0,
-      ).animate(
-        CurvedAnimation(
-          parent: animation,
-          curve: Curves.fastOutSlowIn,
-        ),
-      ),
+      scale: animation.drive(_dialogTween),
     ),
   );
 }
diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart
index 8ec520c..1116501 100644
--- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart
+++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart
@@ -316,7 +316,7 @@
   // animation is complete.
   Color _backgroundColor;
 
-  static final Tween<double> _flexTween = Tween<double>(begin: 1.0, end: 1.5);
+  static final Animatable<double> _flexTween = Tween<double>(begin: 1.0, end: 1.5);
 
   void _resetState() {
     for (AnimationController controller in _controllers)
@@ -655,7 +655,7 @@
       );
       canvas.drawCircle(
         center,
-        radiusTween.lerp(circle.animation.value),
+        radiusTween.transform(circle.animation.value),
         paint,
       );
     }
diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart
index 2d737ac..f80ee1d 100644
--- a/packages/flutter/lib/src/material/chip.dart
+++ b/packages/flutter/lib/src/material/chip.dart
@@ -1220,7 +1220,7 @@
   Animation<double> enableAnimation;
   Animation<double> selectionFade;
 
-  static final Tween<double> pressedShadowTween = Tween<double>(
+  static final Animatable<double> pressedShadowTween = Tween<double>(
     begin: 0.0,
     end: _kPressElevation,
   );
diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart
index f76be29..578d01c 100644
--- a/packages/flutter/lib/src/material/data_table.dart
+++ b/packages/flutter/lib/src/material/data_table.dart
@@ -694,6 +694,9 @@
 
   bool _down;
 
+  static final Animatable<double> _turnTween = Tween<double>(begin: 0.0, end: math.pi)
+    .chain(CurveTween(curve: Curves.easeIn));
+
   @override
   void initState() {
     super.initState();
@@ -706,18 +709,13 @@
     )
     ..addListener(_rebuild);
     _opacityController.value = widget.visible ? 1.0 : 0.0;
-    _orientationAnimation = Tween<double>(
-      begin: 0.0,
-      end: math.pi,
-    ).animate(CurvedAnimation(
-      parent: _orientationController = AnimationController(
-        duration: widget.duration,
-        vsync: this,
-      ),
-      curve: Curves.easeIn
-    ))
-    ..addListener(_rebuild)
-    ..addStatusListener(_resetOrientationAnimation);
+    _orientationController = AnimationController(
+      duration: widget.duration,
+      vsync: this,
+    );
+    _orientationAnimation = _orientationController.drive(_turnTween)
+      ..addListener(_rebuild)
+      ..addStatusListener(_resetOrientationAnimation);
     if (widget.visible)
       _orientationOffset = widget.down ? 0.0 : math.pi;
   }
diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart
index f6c1e7b..602b9bd 100644
--- a/packages/flutter/lib/src/material/date_picker.dart
+++ b/packages/flutter/lib/src/material/date_picker.dart
@@ -525,6 +525,9 @@
 }
 
 class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStateMixin {
+  static final Animatable<double> _chevronOpacityTween = Tween<double>(begin: 1.0, end: 0.0)
+    .chain(CurveTween(curve: Curves.easeInOut));
+
   @override
   void initState() {
     super.initState();
@@ -538,12 +541,7 @@
     _chevronOpacityController = AnimationController(
       duration: const Duration(milliseconds: 250), vsync: this
     );
-    _chevronOpacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
-      CurvedAnimation(
-        parent: _chevronOpacityController,
-        curve: Curves.easeInOut,
-      )
-    );
+    _chevronOpacityAnimation = _chevronOpacityController.drive(_chevronOpacityTween);
   }
 
   @override
diff --git a/packages/flutter/lib/src/material/expand_icon.dart b/packages/flutter/lib/src/material/expand_icon.dart
index ee87f5e..943767c 100644
--- a/packages/flutter/lib/src/material/expand_icon.dart
+++ b/packages/flutter/lib/src/material/expand_icon.dart
@@ -67,16 +67,14 @@
   AnimationController _controller;
   Animation<double> _iconTurns;
 
+  static final Animatable<double> _iconTurnTween = Tween<double>(begin: 0.0, end: 0.5)
+    .chain(CurveTween(curve: Curves.fastOutSlowIn));
+
   @override
   void initState() {
     super.initState();
     _controller = AnimationController(duration: kThemeAnimationDuration, vsync: this);
-    _iconTurns = Tween<double>(begin: 0.0, end: 0.5).animate(
-      CurvedAnimation(
-        parent: _controller,
-        curve: Curves.fastOutSlowIn
-      )
-    );
+    _iconTurns = _controller.drive(_iconTurnTween);
     // If the widget is initially expanded, rotate the icon without animating it.
     if (widget.isExpanded) {
       _controller.value = math.pi;
diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart
index 0254c61..b9f9141 100644
--- a/packages/flutter/lib/src/material/expansion_tile.dart
+++ b/packages/flutter/lib/src/material/expansion_tile.dart
@@ -79,14 +79,22 @@
 }
 
 class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProviderStateMixin {
+  static final Animatable<double> _easeOutTween = CurveTween(curve: Curves.easeOut);
+  static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
+  static final Animatable<double> _halfTween = Tween<double>(begin: 0.0, end: 0.5);
+
+  final ColorTween _borderColorTween = ColorTween();
+  final ColorTween _headerColorTween = ColorTween();
+  final ColorTween _iconColorTween = ColorTween();
+  final ColorTween _backgroundColorTween = ColorTween();
+
   AnimationController _controller;
-  CurvedAnimation _easeOutAnimation;
-  CurvedAnimation _easeInAnimation;
-  ColorTween _borderColor;
-  ColorTween _headerColor;
-  ColorTween _iconColor;
-  ColorTween _backgroundColor;
   Animation<double> _iconTurns;
+  Animation<double> _heightFactor;
+  Animation<Color> _borderColor;
+  Animation<Color> _headerColor;
+  Animation<Color> _iconColor;
+  Animation<Color> _backgroundColor;
 
   bool _isExpanded = false;
 
@@ -94,13 +102,12 @@
   void initState() {
     super.initState();
     _controller = AnimationController(duration: _kExpand, vsync: this);
-    _easeOutAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeOut);
-    _easeInAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
-    _borderColor = ColorTween();
-    _headerColor = ColorTween();
-    _iconColor = ColorTween();
-    _iconTurns = Tween<double>(begin: 0.0, end: 0.5).animate(_easeInAnimation);
-    _backgroundColor = ColorTween();
+    _heightFactor = _controller.drive(_easeInTween);
+    _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
+    _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
+    _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
+    _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
+    _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween));
 
     _isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
     if (_isExpanded)
@@ -116,14 +123,17 @@
   void _handleTap() {
     setState(() {
       _isExpanded = !_isExpanded;
-      if (_isExpanded)
+      if (_isExpanded) {
         _controller.forward();
-      else
-        _controller.reverse().then<void>((Null value) {
+      } else {
+        _controller.reverse().then<void>((void value) {
+          if (!mounted)
+            return;
           setState(() {
             // Rebuild without widget.children.
           });
         });
+      }
       PageStorage.of(context)?.writeState(context, _isExpanded);
     });
     if (widget.onExpansionChanged != null)
@@ -131,12 +141,12 @@
   }
 
   Widget _buildChildren(BuildContext context, Widget child) {
-    final Color borderSideColor = _borderColor.evaluate(_easeOutAnimation) ?? Colors.transparent;
-    final Color titleColor = _headerColor.evaluate(_easeInAnimation);
+    final Color borderSideColor = _borderColor.value ?? Colors.transparent;
+    final Color titleColor = _headerColor.value;
 
     return Container(
       decoration: BoxDecoration(
-        color: _backgroundColor.evaluate(_easeOutAnimation) ?? Colors.transparent,
+        color: _backgroundColor.value ?? Colors.transparent,
         border: Border(
           top: BorderSide(color: borderSideColor),
           bottom: BorderSide(color: borderSideColor),
@@ -146,7 +156,7 @@
         mainAxisSize: MainAxisSize.min,
         children: <Widget>[
           IconTheme.merge(
-            data: IconThemeData(color: _iconColor.evaluate(_easeInAnimation)),
+            data: IconThemeData(color: _iconColor.value),
             child: ListTile(
               onTap: _handleTap,
               leading: widget.leading,
@@ -162,7 +172,7 @@
           ),
           ClipRect(
             child: Align(
-              heightFactor: _easeInAnimation.value,
+              heightFactor: _heightFactor.value,
               child: child,
             ),
           ),
@@ -172,17 +182,23 @@
   }
 
   @override
-  Widget build(BuildContext context) {
+  void didChangeDependencies() {
     final ThemeData theme = Theme.of(context);
-    _borderColor.end = theme.dividerColor;
-    _headerColor
+    _borderColorTween
+      ..end = theme.dividerColor;
+    _headerColorTween
       ..begin = theme.textTheme.subhead.color
       ..end = theme.accentColor;
-    _iconColor
+    _iconColorTween
       ..begin = theme.unselectedWidgetColor
       ..end = theme.accentColor;
-    _backgroundColor.end = widget.backgroundColor;
+    _backgroundColorTween
+      ..end = widget.backgroundColor;
+    super.didChangeDependencies();
+  }
 
+  @override
+  Widget build(BuildContext context) {
     final bool closed = !_isExpanded && _controller.isDismissed;
     return AnimatedBuilder(
       animation: _controller.view,
diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart
index a53abd7..1f37b47 100644
--- a/packages/flutter/lib/src/material/flexible_space_bar.dart
+++ b/packages/flutter/lib/src/material/flexible_space_bar.dart
@@ -133,7 +133,7 @@
         return 0.0;
       case CollapseMode.parallax:
         final double deltaExtent = settings.maxExtent - settings.minExtent;
-        return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).lerp(t);
+        return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).transform(t);
     }
     return null;
   }
@@ -193,7 +193,7 @@
           color: titleStyle.color.withOpacity(opacity)
         );
         final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme);
-        final double scaleValue = Tween<double>(begin: 1.5, end: 1.0).lerp(t);
+        final double scaleValue = Tween<double>(begin: 1.5, end: 1.0).transform(t);
         final Matrix4 scaleTransform = Matrix4.identity()
           ..scale(scaleValue, scaleValue, 1.0);
         final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle);
diff --git a/packages/flutter/lib/src/material/floating_action_button_location.dart b/packages/flutter/lib/src/material/floating_action_button_location.dart
index 5f4d196..3da2248 100644
--- a/packages/flutter/lib/src/material/floating_action_button_location.dart
+++ b/packages/flutter/lib/src/material/floating_action_button_location.dart
@@ -332,25 +332,28 @@
     // then from 0 back to 1 in the second half.
     const Curve curve = Interval(0.5, 1.0, curve: Curves.ease);
     return _AnimationSwap<double>(
-      ReverseAnimation(CurveTween(curve: curve.flipped).animate(parent)),
-      CurveTween(curve: curve).animate(parent),
+      ReverseAnimation(parent.drive(CurveTween(curve: curve.flipped))),
+      parent.drive(CurveTween(curve: curve)),
       parent,
       0.5,
     );
   }
 
+  // Because we only see the last half of the rotation tween,
+  // it needs to go twice as far.
+  static final Animatable<double> _rotationTween = Tween<double>(
+    begin: 1.0 - kFloatingActionButtonTurnInterval * 2.0,
+    end: 1.0,
+  );
+
+  static final Animatable<double> _thresholdCenterTween = CurveTween(curve: const Threshold(0.5));
+
   @override
   Animation<double> getRotationAnimation({Animation<double> parent}) {
-    // Because we only see the last half of the rotation tween,
-    // it needs to go twice as far.
-    final Tween<double> rotationTween = Tween<double>(
-      begin: 1.0 - kFloatingActionButtonTurnInterval * 2,
-      end: 1.0,
-    );
     // This rotation will turn on the way in, but not on the way out.
     return _AnimationSwap<double>(
-      rotationTween.animate(parent),
-      ReverseAnimation(CurveTween(curve: const Threshold(0.5)).animate(parent)),
+      parent.drive(_rotationTween),
+      ReverseAnimation(parent.drive(_thresholdCenterTween)),
       parent,
       0.5,
     );
diff --git a/packages/flutter/lib/src/material/ink_highlight.dart b/packages/flutter/lib/src/material/ink_highlight.dart
index 591654c..23cad86c 100644
--- a/packages/flutter/lib/src/material/ink_highlight.dart
+++ b/packages/flutter/lib/src/material/ink_highlight.dart
@@ -58,10 +58,10 @@
       ..addListener(controller.markNeedsPaint)
       ..addStatusListener(_handleAlphaStatusChanged)
       ..forward();
-    _alpha = IntTween(
+    _alpha = _alphaController.drive(IntTween(
       begin: 0,
-      end: color.alpha
-    ).animate(_alphaController);
+      end: color.alpha,
+    ));
 
     controller.addInkFeature(this);
   }
diff --git a/packages/flutter/lib/src/material/ink_ripple.dart b/packages/flutter/lib/src/material/ink_ripple.dart
index 0cae37b..b42f90e 100644
--- a/packages/flutter/lib/src/material/ink_ripple.dart
+++ b/packages/flutter/lib/src/material/ink_ripple.dart
@@ -96,6 +96,9 @@
   /// or material [Theme].
   static const InteractiveInkFeatureFactory splashFactory = _InkRippleFactory();
 
+  static final Animatable<double> _easeCurveTween = CurveTween(curve: Curves.ease);
+  static final Animatable<double> _fadeOutIntervalTween = CurveTween(curve: const Interval(_kFadeOutIntervalStart, 1.0));
+
   /// Begin a ripple, centered at [position] relative to [referenceBox].
   ///
   /// The [controller] argument is typically obtained via
@@ -140,10 +143,10 @@
     _fadeInController = AnimationController(duration: _kFadeInDuration, vsync: controller.vsync)
       ..addListener(controller.markNeedsPaint)
       ..forward();
-    _fadeIn = IntTween(
+    _fadeIn = _fadeInController.drive(IntTween(
       begin: 0,
       end: color.alpha,
-    ).animate(_fadeInController);
+    ));
 
     // Controls the splash radius and its center. Starts upon confirm.
     _radiusController = AnimationController(duration: _kUnconfirmedRippleDuration, vsync: controller.vsync)
@@ -151,14 +154,11 @@
       ..forward();
      // Initial splash diameter is 60% of the target diameter, final
      // diameter is 10dps larger than the target diameter.
-    _radius = Tween<double>(
-      begin: _targetRadius * 0.30,
-      end: _targetRadius + 5.0,
-    ).animate(
-      CurvedAnimation(
-        parent: _radiusController,
-        curve: Curves.ease,
-      )
+    _radius = _radiusController.drive(
+      Tween<double>(
+        begin: _targetRadius * 0.30,
+        end: _targetRadius + 5.0,
+      ).chain(_easeCurveTween),
     );
 
     // Controls the splash radius and its center. Starts upon confirm however its
@@ -166,14 +166,11 @@
     _fadeOutController = AnimationController(duration: _kFadeOutDuration, vsync: controller.vsync)
       ..addListener(controller.markNeedsPaint)
       ..addStatusListener(_handleAlphaStatusChanged);
-    _fadeOut = IntTween(
-      begin: color.alpha,
-      end: 0,
-    ).animate(
-      CurvedAnimation(
-        parent: _fadeOutController,
-        curve: const Interval(_kFadeOutIntervalStart, 1.0)
-      ),
+    _fadeOut = _fadeOutController.drive(
+      IntTween(
+        begin: color.alpha,
+        end: 0,
+      ).chain(_fadeOutIntervalTween),
     );
 
     controller.addInkFeature(this);
diff --git a/packages/flutter/lib/src/material/ink_splash.dart b/packages/flutter/lib/src/material/ink_splash.dart
index 766abc1..96482f8 100644
--- a/packages/flutter/lib/src/material/ink_splash.dart
+++ b/packages/flutter/lib/src/material/ink_splash.dart
@@ -140,17 +140,17 @@
     _radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
       ..addListener(controller.markNeedsPaint)
       ..forward();
-    _radius = Tween<double>(
+    _radius = _radiusController.drive(Tween<double>(
       begin: _kSplashInitialSize,
-      end: _targetRadius
-    ).animate(_radiusController);
+      end: _targetRadius,
+    ));
     _alphaController = AnimationController(duration: _kSplashFadeDuration, vsync: controller.vsync)
       ..addListener(controller.markNeedsPaint)
       ..addStatusListener(_handleAlphaStatusChanged);
-    _alpha = IntTween(
+    _alpha = _alphaController.drive(IntTween(
       begin: color.alpha,
-      end: 0
-    ).animate(_alphaController);
+      end: 0,
+    ));
 
     controller.addInkFeature(this);
   }
diff --git a/packages/flutter/lib/src/material/page.dart b/packages/flutter/lib/src/material/page.dart
index 82a989e..cd9e987 100644
--- a/packages/flutter/lib/src/material/page.dart
+++ b/packages/flutter/lib/src/material/page.dart
@@ -8,7 +8,7 @@
 import 'theme.dart';
 
 // Fractional offset from 1/4 screen below the top to fully on screen.
-final Tween<Offset> _kBottomUpTween = Tween<Offset>(
+final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
   begin: const Offset(0.0, 0.25),
   end: Offset.zero,
 );
@@ -18,18 +18,17 @@
   _MountainViewPageTransition({
     Key key,
     @required bool fade,
-    @required Animation<double> routeAnimation,
+    @required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
     @required this.child,
-  }) : _positionAnimation = _kBottomUpTween.animate(CurvedAnimation(
-         parent: routeAnimation, // The route's linear 0.0 - 1.0 animation.
-         curve: Curves.fastOutSlowIn,
-       )),
-       _opacityAnimation = fade ? CurvedAnimation(
-         parent: routeAnimation,
-         curve: Curves.easeIn, // Eyeballed from other Material apps.
-       ) : const AlwaysStoppedAnimation<double>(1.0),
+  }) : _positionAnimation = routeAnimation.drive(_kBottomUpTween.chain(_fastOutSlowInTween)),
+       _opacityAnimation = fade
+         ? routeAnimation.drive(_easeInTween) // Eyeballed from other Material apps.
+         : const AlwaysStoppedAnimation<double>(1.0),
        super(key: key);
 
+  static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
+  static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
+
   final Animation<Offset> _positionAnimation;
   final Animation<double> _opacityAnimation;
   final Widget child;
diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart
index da410e9..3a3602b 100644
--- a/packages/flutter/lib/src/material/refresh_indicator.dart
+++ b/packages/flutter/lib/src/material/refresh_indicator.dart
@@ -150,36 +150,32 @@
   bool _isIndicatorAtTop;
   double _dragOffset;
 
+  static final Animatable<double> _threeQuarterTween = Tween<double>(begin: 0.0, end: 0.75);
+  static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
+  static final Animatable<double> _oneToZeroTween = Tween<double>(begin: 1.0, end: 0.0);
+
   @override
   void initState() {
     super.initState();
     _positionController = AnimationController(vsync: this);
-    _positionFactor = Tween<double>(
-      begin: 0.0,
-      end: _kDragSizeFactorLimit,
-    ).animate(_positionController);
-    _value = Tween<double>( // The "value" of the circular progress indicator during a drag.
-      begin: 0.0,
-      end: 0.75,
-    ).animate(_positionController);
+    _positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
+    _value = _positionController.drive(_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
 
     _scaleController = AnimationController(vsync: this);
-    _scaleFactor = Tween<double>(
-      begin: 1.0,
-      end: 0.0,
-    ).animate(_scaleController);
+    _scaleFactor = _scaleController.drive(_oneToZeroTween);
   }
 
   @override
   void didChangeDependencies() {
     final ThemeData theme = Theme.of(context);
-    _valueColor = ColorTween(
-      begin: (widget.color ?? theme.accentColor).withOpacity(0.0),
-      end: (widget.color ?? theme.accentColor).withOpacity(1.0)
-    ).animate(CurvedAnimation(
-      parent: _positionController,
-      curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)
-    ));
+    _valueColor = _positionController.drive(
+      ColorTween(
+        begin: (widget.color ?? theme.accentColor).withOpacity(0.0),
+        end: (widget.color ?? theme.accentColor).withOpacity(1.0)
+      ).chain(CurveTween(
+        curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)
+      )),
+    );
     super.didChangeDependencies();
   }
 
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 175fa14..9d0c6c6 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -536,6 +536,11 @@
     }
   }
 
+  static final Animatable<double> _entranceTurnTween = Tween<double>(
+    begin: 1.0 - kFloatingActionButtonTurnInterval,
+    end: 1.0,
+  ).chain(CurveTween(curve: Curves.easeIn));
+
   void _updateAnimations() {
     // Get the animations for exit and entrance.
     final CurvedAnimation previousExitScaleAnimation = CurvedAnimation(
@@ -553,15 +558,7 @@
       parent: _currentController,
       curve: Curves.easeIn,
     );
-    final Animation<double> currentEntranceRotationAnimation = Tween<double>(
-      begin: 1.0 - kFloatingActionButtonTurnInterval,
-      end: 1.0,
-    ).animate(
-      CurvedAnimation(
-        parent: _currentController,
-        curve: Curves.easeIn
-      ),
-    );
+    final Animation<double> currentEntranceRotationAnimation = _currentController.drive(_entranceTurnTween);
 
     // Get the animations for when the FAB is moving.
     final Animation<double> moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation);
@@ -570,10 +567,7 @@
     // Aggregate the animations.
     _previousScaleAnimation = AnimationMin<double>(moveScaleAnimation, previousExitScaleAnimation);
     _currentScaleAnimation = AnimationMin<double>(moveScaleAnimation, currentEntranceScaleAnimation);
-    _extendedCurrentScaleAnimation = CurvedAnimation(
-      parent: _currentScaleAnimation,
-      curve: const Interval(0.0, 0.1),
-    );
+    _extendedCurrentScaleAnimation = _currentScaleAnimation.drive(CurveTween(curve: const Interval(0.0, 0.1)));
 
     _previousRotationAnimation = TrainHoppingAnimation(previousExitRotationAnimation, moveRotationAnimation);
     _currentRotationAnimation = TrainHoppingAnimation(currentEntranceRotationAnimation, moveRotationAnimation);
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index 6890f53..36ac5df 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -587,7 +587,7 @@
   static const double _preferredTrackWidth = 144.0;
   static const double _preferredTotalWidth = _preferredTrackWidth + _overlayDiameter;
   static const Duration _minimumInteractionTime = Duration(milliseconds: 500);
-  static final Tween<double> _overlayRadiusTween = Tween<double>(begin: 0.0, end: _overlayRadius);
+  static final Animatable<double> _overlayRadiusTween = Tween<double>(begin: 0.0, end: _overlayRadius);
 
   _SliderState _state;
   Animation<double> _overlayAnimation;
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index a40bfcc..d9739a9 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -1010,10 +1010,10 @@
       vsync: this,
     );
     _thetaTween = Tween<double>(begin: _getThetaForTime(widget.selectedTime));
-    _theta = _thetaTween.animate(CurvedAnimation(
-      parent: _thetaController,
-      curve: Curves.fastOutSlowIn
-    ))..addListener(() => setState(() { }));
+    _theta = _thetaController
+      .drive(CurveTween(curve: Curves.fastOutSlowIn))
+      .drive(_thetaTween)
+      ..addListener(() => setState(() { /* _theta.value has changed */ }));
   }
 
   ThemeData themeData;
diff --git a/packages/flutter/lib/src/material/toggleable.dart b/packages/flutter/lib/src/material/toggleable.dart
index bee9e4e..571b5fb 100644
--- a/packages/flutter/lib/src/material/toggleable.dart
+++ b/packages/flutter/lib/src/material/toggleable.dart
@@ -11,7 +11,7 @@
 import 'constants.dart';
 
 const Duration _kToggleDuration = Duration(milliseconds: 200);
-final Tween<double> _kRadialReactionRadiusTween = Tween<double>(begin: 0.0, end: kRadialReactionRadius);
+final Animatable<double> _kRadialReactionRadiusTween = Tween<double>(begin: 0.0, end: kRadialReactionRadius);
 
 /// A base class for material style toggleable controls with toggle animations.
 ///
diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
index 12e0388..f94353a 100644
--- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
+++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
@@ -439,15 +439,14 @@
         markNeedsLayout();
       });
 
-    // Recreating the animation rather than updating a cached value, only
-    // to avoid the extra complexity of managing the animation's lifetime.
-    _animation = Tween<double>(
-      begin: _effectiveScrollOffset,
-      end: direction == ScrollDirection.forward ? 0.0 : maxExtent,
-    ).animate(CurvedAnimation(
-      parent: _controller,
-      curve: snapConfiguration.curve,
-    ));
+    _animation = _controller.drive(
+      Tween<double>(
+        begin: _effectiveScrollOffset,
+        end: direction == ScrollDirection.forward ? 0.0 : maxExtent,
+      ).chain(CurveTween(
+        curve: snapConfiguration.curve,
+      )),
+    );
 
     _controller.forward(from: 0.0);
   }
diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart
index 13d64f9..7812b31 100644
--- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart
+++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart
@@ -243,29 +243,19 @@
       _controller.value = 1.0;
     _firstAnimation = _initAnimation(widget.firstCurve, true);
     _secondAnimation = _initAnimation(widget.secondCurve, false);
-  }
-
-  Animation<double> _initAnimation(Curve curve, bool inverted) {
-    Animation<double> animation = CurvedAnimation(
-      parent: _controller,
-      curve: curve,
-    );
-
-    if (inverted) {
-      animation = Tween<double>(
-        begin: 1.0,
-        end: 0.0,
-      ).animate(animation);
-    }
-
-    animation.addStatusListener((AnimationStatus status) {
+    _controller.addStatusListener((AnimationStatus status) {
       setState(() {
         // Trigger a rebuild because it depends on _isTransitioning, which
         // changes its value together with animation status.
       });
     });
+  }
 
-    return animation;
+  Animation<double> _initAnimation(Curve curve, bool inverted) {
+    Animation<double> result = _controller.drive(CurveTween(curve: curve));
+    if (inverted)
+      result = result.drive(Tween<double>(begin: 1.0, end: 0.0));
+    return result;
   }
 
   @override
@@ -302,8 +292,8 @@
   Widget build(BuildContext context) {
     const Key kFirstChildKey = ValueKey<CrossFadeState>(CrossFadeState.showFirst);
     const Key kSecondChildKey = ValueKey<CrossFadeState>(CrossFadeState.showSecond);
-    final bool transitioningForwards = _controller.status == AnimationStatus.completed || _controller.status == AnimationStatus.forward;
-
+    final bool transitioningForwards = _controller.status == AnimationStatus.completed ||
+                                       _controller.status == AnimationStatus.forward;
     Key topKey;
     Widget topChild;
     Animation<double> topAnimation;
diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart
index 27b3a8f..f945da4 100644
--- a/packages/flutter/lib/src/widgets/dismissible.dart
+++ b/packages/flutter/lib/src/widgets/dismissible.dart
@@ -323,12 +323,14 @@
 
   void _updateMoveAnimation() {
     final double end = _dragExtent.sign;
-    _moveAnimation = Tween<Offset>(
-      begin: Offset.zero,
-      end: _directionIsXAxis
-          ? Offset(end, widget.crossAxisEndOffset)
-          : Offset(widget.crossAxisEndOffset, end),
-    ).animate(_moveController);
+    _moveAnimation = _moveController.drive(
+      Tween<Offset>(
+        begin: Offset.zero,
+        end: _directionIsXAxis
+               ? Offset(end, widget.crossAxisEndOffset)
+               : Offset(widget.crossAxisEndOffset, end),
+      ),
+    );
   }
 
   _FlingGestureKind _describeFlingGesture(Velocity velocity) {
@@ -424,13 +426,16 @@
       _resizeController.forward();
       setState(() {
         _sizePriorToCollapse = context.size;
-        _resizeAnimation = Tween<double>(
-          begin: 1.0,
-          end: 0.0
-        ).animate(CurvedAnimation(
-          parent: _resizeController,
-          curve: _kResizeTimeCurve
-        ));
+        _resizeAnimation = _resizeController.drive(
+          CurveTween(
+            curve: _kResizeTimeCurve
+          ),
+        ).drive(
+          Tween<double>(
+            begin: 1.0,
+            end: 0.0
+          ),
+        );
       });
     }
   }
diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index a5a6329..fe55d7a 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -326,6 +326,8 @@
     return RectTween(begin: begin, end: end);
   }
 
+  static final Animatable<double> _reverseTween = Tween<double>(begin: 1.0, end: 0.0);
+
   // The OverlayEntry WidgetBuilder callback for the hero's overlay.
   Widget _buildOverlay(BuildContext context) {
     assert(manifest != null);
@@ -347,9 +349,9 @@
           // The toHero no longer exists or it's no longer the flight's destination.
           // Continue flying while fading out.
           if (_heroOpacity.isCompleted) {
-            _heroOpacity = Tween<double>(begin: 1.0, end: 0.0)
-              .chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0)))
-              .animate(_proxyAnimation);
+            _heroOpacity = _proxyAnimation.drive(
+              _reverseTween.chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0))),
+            );
           }
         } else if (toHeroBox.hasSize) {
           // The toHero has been laid out. If it's no longer where the hero animation is
@@ -460,10 +462,12 @@
       assert(manifest.toHero == newManifest.fromHero);
       assert(manifest.toRoute == newManifest.fromRoute);
 
-      _proxyAnimation.parent = Tween<double>(
-        begin: manifest.animation.value,
-        end: 1.0,
-      ).animate(newManifest.animation);
+      _proxyAnimation.parent = newManifest.animation.drive(
+        Tween<double>(
+          begin: manifest.animation.value,
+          end: 1.0,
+        ),
+      );
 
       if (manifest.fromHero != newManifest.toHero) {
         manifest.fromHero.endFlight();
diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart
index 7bf804d..7332c88 100644
--- a/packages/flutter/lib/src/widgets/implicit_animations.dart
+++ b/packages/flutter/lib/src/widgets/implicit_animations.dart
@@ -1105,7 +1105,7 @@
 
   @override
   void didUpdateTweens() {
-    _opacityAnimation = _opacity.animate(animation);
+    _opacityAnimation = animation.drive(_opacity);
   }
 
   @override
diff --git a/packages/flutter/lib/src/widgets/overscroll_indicator.dart b/packages/flutter/lib/src/widgets/overscroll_indicator.dart
index 6da2292..5a59825 100644
--- a/packages/flutter/lib/src/widgets/overscroll_indicator.dart
+++ b/packages/flutter/lib/src/widgets/overscroll_indicator.dart
@@ -259,8 +259,8 @@
       parent: _glowController,
       curve: Curves.decelerate,
     )..addListener(notifyListeners);
-    _glowOpacity = _glowOpacityTween.animate(decelerator);
-    _glowSize = _glowSizeTween.animate(decelerator);
+    _glowOpacity = decelerator.drive(_glowOpacityTween);
+    _glowSize = decelerator.drive(_glowSizeTween);
     _displacementTicker = vsync.createTicker(_tickDisplacement);
   }
 
diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart
index 252b4cc..fd6d3df 100644
--- a/packages/flutter/lib/src/widgets/routes.dart
+++ b/packages/flutter/lib/src/widgets/routes.dart
@@ -16,6 +16,9 @@
 import 'page_storage.dart';
 import 'transitions.dart';
 
+// Examples can assume:
+// dynamic routeObserver;
+
 const Color _kTransparent = Color(0x00000000);
 
 /// A route that displays widgets in the [Navigator]'s [Overlay].
@@ -1171,19 +1174,20 @@
   final GlobalKey _subtreeKey = GlobalKey();
   final PageStorageBucket _storageBucket = PageStorageBucket();
 
+  static final Animatable<double> _easeCurveTween = CurveTween(curve: Curves.ease);
+
   // one of the builders
   OverlayEntry _modalBarrier;
   Widget _buildModalBarrier(BuildContext context) {
     Widget barrier;
     if (barrierColor != null && !offstage) { // changedInternalState is called if these update
       assert(barrierColor != _kTransparent);
-      final Animation<Color> color = ColorTween(
-        begin: _kTransparent,
-        end: barrierColor, // changedInternalState is called if this updates
-      ).animate(CurvedAnimation(
-        parent: animation,
-        curve: Curves.ease,
-      ));
+      final Animation<Color> color = animation.drive(
+        ColorTween(
+          begin: _kTransparent,
+          end: barrierColor, // changedInternalState is called if this updates
+        ).chain(_easeCurveTween),
+      );
       barrier = AnimatedModalBarrier(
         color: color,
         dismissible: barrierDismissible, // changedInternalState is called if this updates
diff --git a/packages/flutter/test/animation/tween_test.dart b/packages/flutter/test/animation/tween_test.dart
index b8b77a5..fda95dc 100644
--- a/packages/flutter/test/animation/tween_test.dart
+++ b/packages/flutter/test/animation/tween_test.dart
@@ -21,7 +21,7 @@
     expect(chain, hasOneLineDescription);
   });
 
-  test('Can animated tweens', () {
+  test('Can animate tweens', () {
     final Tween<double> tween = Tween<double>(begin: 0.30, end: 0.50);
     final AnimationController controller = AnimationController(
       vsync: const TestVSync(),
@@ -33,6 +33,18 @@
     expect(animation.toStringDetails(), hasOneLineDescription);
   });
 
+  test('Can drive tweens', () {
+    final Tween<double> tween = Tween<double>(begin: 0.30, end: 0.50);
+    final AnimationController controller = AnimationController(
+      vsync: const TestVSync(),
+    );
+    final Animation<double> animation = controller.drive(tween);
+    controller.value = 0.50;
+    expect(animation.value, 0.40);
+    expect(animation, hasOneLineDescription);
+    expect(animation.toStringDetails(), hasOneLineDescription);
+  });
+
   test('SizeTween', () {
     final SizeTween tween = SizeTween(begin: Size.zero, end: const Size(20.0, 30.0));
     expect(tween.lerp(0.5), equals(const Size(10.0, 15.0)));
diff --git a/packages/flutter/test/material/arc_test.dart b/packages/flutter/test/material/arc_test.dart
index 74041f4..48776da 100644
--- a/packages/flutter/test/material/arc_test.dart
+++ b/packages/flutter/test/material/arc_test.dart
@@ -18,8 +18,7 @@
     );
 
     expect(a, hasOneLineDescription);
-    expect(a, equals(b));
-    expect(a.hashCode, equals(b.hashCode));
+    expect(a.toString(), equals(b.toString()));
   });
 
   test('MaterialRectArcTween control test', () {
@@ -33,8 +32,7 @@
       end: Rect.fromLTWH(0.0, 10.0, 10.0, 10.0)
     );
     expect(a, hasOneLineDescription);
-    expect(a, equals(b));
-    expect(a.hashCode, equals(b.hashCode));
+    expect(a.toString(), equals(b.toString()));
   });
 
   test('on-axis MaterialPointArcTween', () {
diff --git a/packages/flutter_tools/doc/daemon.md b/packages/flutter_tools/doc/daemon.md
index 5e2c70a..d4de6b8 100644
--- a/packages/flutter_tools/doc/daemon.md
+++ b/packages/flutter_tools/doc/daemon.md
@@ -188,7 +188,7 @@
 - `emulatorName` - the name of the emulator created; this will have been auto-generated if you did not supply one
 - `error` - when `success`=`false`, a message explaining why the creation of the emulator failed
 
-## 'flutter run --machine' and 'flutter attach --machine' 
+## 'flutter run --machine' and 'flutter attach --machine'
 
 When running `flutter run --machine` or `flutter attach --machine` the following subset of the daemon is available:
 
diff --git a/packages/flutter_tools/templates/module/README.md b/packages/flutter_tools/templates/module/README.md
index bae329d..aed3594 100644
--- a/packages/flutter_tools/templates/module/README.md
+++ b/packages/flutter_tools/templates/module/README.md
@@ -80,7 +80,7 @@
 #### host_app_ephemeral_cocoapods
 
 Written to `.ios/` on top of `host_app_ephemeral`.
- 
+
 Adds CocoaPods support.
 
 Combined contents define an ephemeral host app suitable for when the
diff --git a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl b/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl
index bb2d81e..952508a 100644
--- a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl
+++ b/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl
@@ -1,6 +1,6 @@
 import Flutter
 import UIKit
-    
+
 public class Swift{{pluginClass}}: NSObject, FlutterPlugin {
   public static func register(with registrar: FlutterPluginRegistrar) {
     let channel = FlutterMethodChannel(name: "{{projectName}}", binaryMessenger: registrar.messenger())
diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl b/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl
index 21911bf..ea668b6 100644
--- a/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl
+++ b/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl
@@ -15,7 +15,7 @@
   s.source_files = 'Classes/**/*'
   s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
-  
+
   s.ios.deployment_target = '8.0'
 end