Add transitions.dart for common animation transitions.

Use those in SnackBar, Drawer, navigator instead of AnimatedContainer's
intentions.
diff --git a/sky/packages/sky/lib/animation/animation_performance.dart b/sky/packages/sky/lib/animation/animation_performance.dart
index d95c11e..7c2e562 100644
--- a/sky/packages/sky/lib/animation/animation_performance.dart
+++ b/sky/packages/sky/lib/animation/animation_performance.dart
@@ -145,7 +145,8 @@
   }
 
   void _tick(double t) {
-    variable.setProgress(t);
+    if (variable != null)
+      variable.setProgress(t);
     _notifyListeners();
     _checkStatusChanged();
   }
diff --git a/sky/packages/sky/lib/widgets/drawer.dart b/sky/packages/sky/lib/widgets/drawer.dart
index 3989262..d075d1a 100644
--- a/sky/packages/sky/lib/widgets/drawer.dart
+++ b/sky/packages/sky/lib/widgets/drawer.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:sky' as sky;
 
+import 'package:sky/animation/animated_value.dart';
 import 'package:sky/animation/animation_performance.dart';
 import 'package:sky/animation/forces.dart';
 import 'package:sky/theme/shadows.dart';
@@ -15,6 +16,7 @@
 import 'package:sky/widgets/navigator.dart';
 import 'package:sky/widgets/scrollable.dart';
 import 'package:sky/widgets/theme.dart';
+import 'package:sky/widgets/transitions.dart';
 
 export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
 
@@ -57,17 +59,11 @@
   DrawerStatusChangedCallback onStatusChanged;
   Navigator navigator;
 
-  SlideInIntention _intention;
-  ColorTransitionIntention _maskColorIntention;
-  AnimationPerformance get _performance => _intention.performance;
+  AnimationPerformance _performance;
 
   void initState() {
-    _intention = new SlideInIntention(
-        duration: _kBaseSettleDuration, start: _kClosedPosition, end: _kOpenPosition);
-    _maskColorIntention = new ColorTransitionIntention(
-        performance: _intention.performance, start: colors.transparent, end: const Color(0x7F000000));
+    _performance = new AnimationPerformance(duration: _kBaseSettleDuration);
 
-    _performance.addStatusListener(_onStatusChanged);
     // Use a spring force for animating the drawer. We can't use curves for
     // this because we need a linear curve in order to track the user's finger
     // while dragging.
@@ -87,27 +83,27 @@
 
   Widget build() {
     var mask = new Listener(
-      child: new AnimatedContainer(
-        intentions: [_maskColorIntention],
-        tag: showing
+      child: new ColorTransition(
+        performance: _performance,
+        direction: showing ? Direction.forward : Direction.reverse,
+        color: new AnimatedColorValue(colors.transparent, end: const Color(0x7F000000))
       ),
       onGestureTap: handleMaskTap
     );
 
-    Widget content = new AnimatedContainer(
-      intentions: [
-        _intention,
-        // TODO(mpcomplete): it should be easier to override some intentions,
-        // and have those you don't care about revert to a sensible default.
-        new ImplicitlySyncDecorationIntention(_kThemeChangeDuration),
-        new ImplicitlySyncWidthIntention(_kThemeChangeDuration),
-      ],
-      tag: showing,
-      decoration: new BoxDecoration(
-        backgroundColor: Theme.of(this).canvasColor,
-        boxShadow: shadows[level]),
-      width: _kWidth,
-      child: new ScrollableBlock(children)
+    Widget content = new SlideIn(
+      performance: _performance,
+      direction: showing ? Direction.forward : Direction.reverse,
+      position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
+      onDismissed: _onDismissed,
+      child: new AnimatedContainer(
+        intentions: implicitlySyncFieldsIntention(const Duration(milliseconds: 200)),
+        decoration: new BoxDecoration(
+          backgroundColor: Theme.of(this).canvasColor,
+          boxShadow: shadows[level]),
+        width: _kWidth,
+        child: new ScrollableBlock(children)
+      )
     );
 
     return new Listener(
@@ -120,6 +116,10 @@
     );
   }
 
+  void _onDismissed() {
+    _onStatusChanged(AnimationStatus.dismissed);
+  }
+
   void _onStatusChanged(AnimationStatus status) {
     scheduleMicrotask(() {
       if (status == AnimationStatus.dismissed &&
diff --git a/sky/packages/sky/lib/widgets/navigator.dart b/sky/packages/sky/lib/widgets/navigator.dart
index 6a1b9cc..0fd913e 100644
--- a/sky/packages/sky/lib/widgets/navigator.dart
+++ b/sky/packages/sky/lib/widgets/navigator.dart
@@ -11,6 +11,7 @@
 import 'package:sky/widgets/animated_component.dart';
 import 'package:sky/widgets/basic.dart';
 import 'package:sky/widgets/focus.dart';
+import 'package:sky/widgets/transitions.dart';
 import 'package:vector_math/vector_math.dart';
 
 typedef Widget RouteBuilder(Navigator navigator, RouteBase route);
@@ -66,76 +67,39 @@
 // and support multiple transition types
 const Duration _kTransitionDuration = const Duration(milliseconds: 150);
 const Point _kTransitionStartPoint = const Point(0.0, 75.0);
-class Transition extends AnimatedComponent {
+class Transition extends TransitionBase {
   Transition({
     Key key,
-    this.content,
-    this.direction,
-    this.onDismissed,
-    this.onCompleted,
+    Widget child,
+    Direction direction,
+    Function onDismissed,
+    Function onCompleted,
     this.interactive
-  }): super(key: key);
-  Widget content;
-  Direction direction;
+  }): super(key: key,
+            child: child,
+            duration: _kTransitionDuration,
+            direction: direction,
+            onDismissed: onDismissed,
+            onCompleted: onCompleted);
   bool interactive;
-  Function onDismissed;
-  Function onCompleted;
-
-  AnimatedValue<Point> _position;
-  AnimatedValue<double> _opacity;
-  AnimationPerformance _performance;
-
-  void initState() {
-    _position = new AnimatedValue<Point>(
-      _kTransitionStartPoint,
-      end: Point.origin,
-      curve: easeOut
-    );
-    _opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut);
-    _performance = new AnimationPerformance(duration: _kTransitionDuration)
-      ..variable = new AnimatedList([_position, _opacity]);
-    if (direction == Direction.reverse)
-      _performance.progress = 1.0;
-    _performance.addStatusListener(_checkStatusChanged);
-    watch(_performance);
-    _start();
-  }
-
-  void _start() {
-    _performance.play(direction);
-  }
 
   void syncFields(Transition source) {
-    content = source.content;
     interactive = source.interactive;
-    onDismissed = source.onDismissed;
-    if (direction != source.direction) {
-      direction = source.direction;
-      _start();
-    }
     super.syncFields(source);
   }
 
-  void _checkStatusChanged(AnimationStatus status) {
-    if (_performance.isDismissed) {
-      if (onDismissed != null)
-        onDismissed();
-    } else if (_performance.isCompleted) {
-      if (onCompleted != null)
-        onCompleted();
-    }
-  }
-
   Widget build() {
-    Matrix4 transform = new Matrix4.identity()
-      ..translate(_position.value.x, _position.value.y);
     // TODO(jackson): Hit testing should ignore transform
     // TODO(jackson): Block input unless content is interactive
-    return new Transform(
-      transform: transform,
-      child: new Opacity(
-        opacity: _opacity.value,
-        child: content
+    return new SlideIn(
+      performance: performance,
+      direction: direction,
+      position: new AnimatedValue<Point>(_kTransitionStartPoint, end: Point.origin, curve: easeOut),
+      child: new FadeIn(
+        performance: performance,
+        direction: direction,
+        opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
+        child: child
       )
     );
   }
@@ -233,16 +197,16 @@
       if (i + 1 < state.history.length && state.history[i + 1].fullyOpaque)
         continue;
       HistoryEntry historyEntry = state.history[i];
-      Widget content = historyEntry.route.build(this, historyEntry.route);
+      Widget child = historyEntry.route.build(this, historyEntry.route);
       if (i == 0) {
-        visibleRoutes.add(content);
+        visibleRoutes.add(child);
         continue;
       }
-      if (content == null)
+      if (child == null)
         continue;
       Transition transition = new Transition(
         key: new Key.fromObjectIdentity(historyEntry),
-        content: content,
+        child: child,
         direction: (i <= state.historyIndex) ? Direction.forward : Direction.reverse,
         interactive: (i == state.historyIndex),
         onDismissed: () {
diff --git a/sky/packages/sky/lib/widgets/snack_bar.dart b/sky/packages/sky/lib/widgets/snack_bar.dart
index 9496ead..4478421 100644
--- a/sky/packages/sky/lib/widgets/snack_bar.dart
+++ b/sky/packages/sky/lib/widgets/snack_bar.dart
@@ -4,15 +4,16 @@
 
 import 'dart:async';
 
+import 'package:sky/animation/animated_value.dart';
 import 'package:sky/animation/animation_performance.dart';
 import 'package:sky/painting/text_style.dart';
 import 'package:sky/theme/typography.dart' as typography;
 import 'package:sky/widgets/animated_container.dart';
-import 'package:sky/widgets/animation_intentions.dart';
 import 'package:sky/widgets/basic.dart';
 import 'package:sky/widgets/default_text_style.dart';
 import 'package:sky/widgets/material.dart';
 import 'package:sky/widgets/theme.dart';
+import 'package:sky/widgets/transitions.dart';
 
 export 'package:sky/animation/animation_performance.dart' show AnimationStatus;
 
@@ -39,8 +40,7 @@
     );
   }
 }
-
-class SnackBar extends StatefulComponent {
+class SnackBar extends Component {
 
   SnackBar({
     Key key,
@@ -57,25 +57,9 @@
   bool showing;
   SnackBarStatusChangedCallback onStatusChanged;
 
-  SlideInIntention _intention;
-
-  void initState() {
-    _intention = new SlideInIntention(duration: _kSlideInDuration,
-                                      start: const Point(0.0, 50.0),
-                                      end: Point.origin);
-    _intention.performance.addStatusListener(_onStatusChanged);
-  }
-
-  void syncFields(SnackBar source) {
-    content = source.content;
-    actions = source.actions;
-    onStatusChanged = source.onStatusChanged;
-    showing = source.showing;
-  }
-
-  void _onStatusChanged(AnimationStatus status) {
+  void _onDismissed() {
     if (onStatusChanged != null)
-      scheduleMicrotask(() { onStatusChanged(status); });
+      scheduleMicrotask(() { onStatusChanged(AnimationStatus.dismissed); });
   }
 
   Widget build() {
@@ -91,9 +75,12 @@
       )
     ]..addAll(actions);
 
-    return new AnimatedContainer(
-      intentions: [_intention],
-      tag: showing,
+    return new SlideIn(
+      duration: _kSlideInDuration,
+      direction: showing ? Direction.forward : Direction.reverse,
+      position: new AnimatedValue<Point>(const Point(0.0, 50.0),
+                                         end: Point.origin),
+      onDismissed: _onDismissed,
       child: new Material(
         level: 2,
         color: const Color(0xFF323232),
diff --git a/sky/packages/sky/lib/widgets/transitions.dart b/sky/packages/sky/lib/widgets/transitions.dart
new file mode 100644
index 0000000..63b0156
--- /dev/null
+++ b/sky/packages/sky/lib/widgets/transitions.dart
@@ -0,0 +1,170 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:sky/widgets/animated_component.dart';
+import 'package:sky/animation/animation_performance.dart';
+import 'package:sky/animation/animated_value.dart';
+import 'package:sky/widgets/animated_component.dart';
+import 'package:sky/widgets/basic.dart';
+import 'package:vector_math/vector_math.dart';
+
+abstract class TransitionBase extends AnimatedComponent {
+  TransitionBase({
+    Key key,
+    this.child,
+    this.direction,
+    this.duration,
+    this.performance,
+    this.onDismissed,
+    this.onCompleted
+  }) : super(key: key);
+
+  Widget child;
+  Direction direction;
+  Duration duration;
+  AnimationPerformance performance;
+  Function onDismissed;
+  Function onCompleted;
+
+  void initState() {
+    if (performance == null) {
+      assert(duration != null);
+      performance = new AnimationPerformance(duration: duration);
+    }
+    if (direction == Direction.reverse)
+      performance.progress = 1.0;
+    performance.addStatusListener(_checkStatusChanged);
+
+    watch(performance);
+    _start();
+  }
+
+  void syncFields(TransitionBase source) {
+    child = source.child;
+    onCompleted = source.onCompleted;
+    onDismissed = source.onDismissed;
+    duration = source.duration;
+    if (direction != source.direction) {
+      direction = source.direction;
+      _start();
+    }
+    super.syncFields(source);
+  }
+
+  void _start() {
+    performance.play(direction);
+  }
+
+  void _checkStatusChanged(AnimationStatus status) {
+    if (performance.isDismissed) {
+      if (onDismissed != null)
+        onDismissed();
+    } else if (performance.isCompleted) {
+      if (onCompleted != null)
+        onCompleted();
+    }
+  }
+
+  Widget build();
+}
+
+class SlideIn extends TransitionBase {
+  // TODO(mpcomplete): this constructor is mostly boilerplate, passing values
+  // to super. Is there a simpler way?
+  SlideIn({
+    Key key,
+    this.position,
+    Duration duration,
+    AnimationPerformance performance,
+    Direction direction,
+    Function onDismissed,
+    Function onCompleted,
+    Widget child
+  }) : super(key: key,
+             duration: duration,
+             performance: performance,
+             direction: direction,
+             onDismissed: onDismissed,
+             onCompleted: onCompleted,
+             child: child);
+
+  AnimatedValue<Point> position;
+
+  void syncFields(SlideIn updated) {
+    position = updated.position;
+    super.syncFields(updated);
+  }
+
+  Widget build() {
+    position.setProgress(performance.progress);
+    Matrix4 transform = new Matrix4.identity()
+      ..translate(position.value.x, position.value.y);
+    return new Transform(transform: transform, child: child);
+  }
+}
+
+class FadeIn extends TransitionBase {
+  FadeIn({
+    Key key,
+    this.opacity,
+    Duration duration,
+    AnimationPerformance performance,
+    Direction direction,
+    Function onDismissed,
+    Function onCompleted,
+    Widget child
+  }) : super(key: key,
+             duration: duration,
+             performance: performance,
+             direction: direction,
+             onDismissed: onDismissed,
+             onCompleted: onCompleted,
+             child: child);
+
+  AnimatedValue<double> opacity;
+
+  void syncFields(FadeIn updated) {
+    opacity = updated.opacity;
+    super.syncFields(updated);
+  }
+
+  Widget build() {
+    opacity.setProgress(performance.progress);
+    return new Opacity(opacity: opacity.value, child: child);
+  }
+}
+
+class ColorTransition extends TransitionBase {
+  ColorTransition({
+    Key key,
+    this.color,
+    Duration duration,
+    AnimationPerformance performance,
+    Direction direction,
+    Function onDismissed,
+    Function onCompleted,
+    Widget child
+  }) : super(key: key,
+             duration: duration,
+             performance: performance,
+             direction: direction,
+             onDismissed: onDismissed,
+             onCompleted: onCompleted,
+             child: child);
+
+  AnimatedColorValue color;
+
+  void syncFields(ColorTransition updated) {
+    color = updated.color;
+    super.syncFields(updated);
+  }
+
+  Widget build() {
+    color.setProgress(performance.progress);
+    return new DecoratedBox(
+      decoration: new BoxDecoration(backgroundColor: color.value),
+      child: child
+    );
+  }
+}