Dismissable provides intrinsic support for resize animation
diff --git a/packages/flutter/example/widgets/card_collection.dart b/packages/flutter/example/widgets/card_collection.dart
index 4d040b2..5680645 100644
--- a/packages/flutter/example/widgets/card_collection.dart
+++ b/packages/flutter/example/widgets/card_collection.dart
@@ -2,13 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:sky/animation/animated_value.dart';
-import 'package:sky/animation/animation_performance.dart';
-import 'package:sky/animation/curves.dart';
 import 'package:sky/base/lerp.dart';
 import 'package:sky/painting/text_style.dart';
 import 'package:sky/theme/colors.dart';
-import 'package:sky/widgets/animated_component.dart';
 import 'package:sky/widgets/basic.dart';
 import 'package:sky/widgets/block_viewport.dart';
 import 'package:sky/widgets/card.dart';
@@ -25,51 +21,10 @@
   int value;
   double height;
   Color color;
-  AnimationPerformance performance;
   String get label => "Item $value";
   Key get key => new Key.fromObjectIdentity(this);
 }
 
-class ShrinkingCard extends AnimatedComponent {
-
-  ShrinkingCard({
-    Key key,
-    CardModel this.card,
-    Function this.onUpdated,
-    Function this.onCompleted
-  }) : super(key: key);
-
-  CardModel card;
-  Function onUpdated;
-  Function onCompleted;
-
-  double get currentHeight => (card.performance.variable as AnimatedValue).value;
-
-  void initState() {
-    assert(card.performance != null);
-    card.performance.addListener(handleAnimationProgress);
-    watch(card.performance);
-  }
-
-  void handleAnimationProgress() {
-    if (card.performance.isCompleted) {
-      if (onCompleted != null)
-        onCompleted();
-    } else if (onUpdated != null) {
-      onUpdated();
-    }
-  }
-
-  void syncFields(ShrinkingCard source) {
-    card = source.card;
-    onCompleted = source.onCompleted;
-    onUpdated = source.onUpdated;
-    super.syncFields(source);
-  }
-
-  Widget build() => new Container(height: currentHeight);
-}
-
 class CardCollectionApp extends App {
 
   final TextStyle cardLabelStyle =
@@ -91,24 +46,6 @@
     super.initState();
   }
 
-  void shrinkCard(CardModel card, int index) {
-    if (card.performance != null)
-      return;
-    layoutState.invalidate([index]);
-    setState(() {
-      assert(card.performance == null);
-      card.performance = new AnimationPerformance()
-        ..duration = const Duration(milliseconds: 300)
-        ..variable = new AnimatedValue<double>(
-          card.height + kCardMargins.top + kCardMargins.bottom,
-          end: 0.0,
-          curve: ease,
-          interval: new Interval(0.5, 1.0)
-        )
-        ..play();
-    });
-  }
-
   void dismissCard(CardModel card) {
     if (cardModels.contains(card)) {
       setState(() {
@@ -122,18 +59,10 @@
       return null;
     CardModel card = cardModels[index];
 
-    if (card.performance != null) {
-      return new ShrinkingCard(
-          key: card.key,
-          card: card,
-          onUpdated: () { layoutState.invalidate([index]); },
-          onCompleted: () { dismissCard(card); }
-      );
-    }
-
     return new Dismissable(
       key: card.key,
-      onDismissed: () { shrinkCard(card, index); },
+      onResized: () { layoutState.invalidate([index]); },
+      onDismissed: () { dismissCard(card); },
       child: new Card(
         color: card.color,
         child: new Container(
diff --git a/packages/flutter/lib/widgets/dismissable.dart b/packages/flutter/lib/widgets/dismissable.dart
index 2ecf4e5..4599c00 100644
--- a/packages/flutter/lib/widgets/dismissable.dart
+++ b/packages/flutter/lib/widgets/dismissable.dart
@@ -6,17 +6,21 @@
 
 import 'package:sky/animation/animated_value.dart';
 import 'package:sky/animation/animation_performance.dart';
+import 'package:sky/animation/curves.dart';
 import 'package:sky/widgets/animated_component.dart';
 import 'package:sky/widgets/basic.dart';
 import 'package:sky/widgets/widget.dart';
 import 'package:vector_math/vector_math.dart';
 
 const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
+const Duration _kCardDismissResize = const Duration(milliseconds: 300);
+const double _kCardDismissResizeDelay = 0.4;
 const double _kMinFlingVelocity = 700.0;
 const double _kMinFlingVelocityDelta = 400.0;
 const double _kFlingVelocityScale = 1.0 / 300.0;
 const double _kDismissCardThreshold = 0.6;
 
+typedef void ResizedCallback();
 typedef void DismissedCallback();
 
 class Dismissable extends AnimatedComponent {
@@ -24,70 +28,106 @@
   Dismissable({
     Key key,
     this.child,
+    this.onResized,
     this.onDismissed
     // TODO(hansmuller): direction
   }) : super(key: key);
 
   Widget child;
+  ResizedCallback onResized;
   DismissedCallback onDismissed;
 
   AnimatedValue<Point> _position;
   AnimatedValue<double> _opacity;
-  AnimationPerformance _performance;
+  AnimationPerformance _fadePerformance;
+  AnimationPerformance _resizePerformance;
 
-  double _width;
+  Size _size;
   double _dragX = 0.0;
   bool _dragUnderway = false;
 
   void initState() {
     _position = new AnimatedValue<Point>(Point.origin);
     _opacity = new AnimatedValue<double>(1.0, end: 0.0);
-    _performance = new AnimationPerformance()
+    _fadePerformance = new AnimationPerformance()
       ..duration = _kCardDismissFadeout
       ..variable = new AnimatedList([_position, _opacity])
-      ..addListener(_handleAnimationProgressChanged);
-    watch(_performance);
+      ..addListener(_handleFadeProgressChanged);
+    watch(_fadePerformance);
+  }
+
+  void _handleFadeProgressChanged() {
+    setState(() {
+      if (_fadePerformance.isCompleted && !_dragUnderway)
+        _startResizePerformance();
+    });
   }
 
   void syncFields(Dismissable source) {
     child = source.child;
+    onResized = source.onResized;
     onDismissed = source.onDismissed;
     super.syncFields(source);
   }
 
   Point get _activeCardDragEndPoint {
-    assert(_width != null);
-    return new Point(_dragX.sign * _width * _kDismissCardThreshold, 0.0);
+    assert(_size != null);
+    return new Point(_dragX.sign * _size.width * _kDismissCardThreshold, 0.0);
   }
 
   bool get _isActive {
-    return _width != null && (_dragUnderway || _performance.isAnimating);
+    return _size != null && (_dragUnderway || _fadePerformance.isAnimating);
+  }
+
+  void _maybeCallOnResized() {
+    if (onResized != null)
+      onResized();
   }
 
   void _maybeCallOnDismissed() {
-    _performance.stop();
-    _performance.removeListener(_handleAnimationProgressChanged);
+    _resizePerformance.stop();
+    _resizePerformance.removeListener(_handleResizeProgressChanged);
     if (onDismissed != null)
       onDismissed();
   }
 
-  void _handleAnimationProgressChanged() {
-    setState(() {
-      if (_performance.isCompleted && !_dragUnderway)
-        _maybeCallOnDismissed();
-    });
+  void _startResizePerformance() {
+    assert(_size != null);
+    assert(_fadePerformance != null);
+    assert(_resizePerformance == null);
+
+    _fadePerformance.stop();
+    _fadePerformance.removeListener(_handleFadeProgressChanged);
+
+    _maybeCallOnResized();
+
+    AnimatedValue<double> dismissHeight = new AnimatedValue<double>(_size.height,
+        end: 0.0,
+        curve: ease,
+        interval: new Interval(_kCardDismissResizeDelay, 1.0)
+    );
+    _resizePerformance = new AnimationPerformance()
+      ..variable = dismissHeight
+      ..duration = _kCardDismissResize
+      ..addListener(_handleResizeProgressChanged)
+      ..play();
+    watch(_resizePerformance);
   }
 
-  void _handleSizeChanged(Size newSize) {
-    _width = newSize.width;
-    _position.end = _activeCardDragEndPoint;
+  void _handleResizeProgressChanged() {
+    setState(() {
+      if (_resizePerformance.isCompleted)
+        _maybeCallOnDismissed();
+      else
+        _maybeCallOnResized();
+    });
   }
 
   void _handlePointerDown(sky.PointerEvent event) {
     setState(() {
       _dragUnderway = true;
       _dragX = 0.0;
-      _performance.progress = 0.0;
+      _fadePerformance.progress = 0.0;
     });
   }
 
@@ -98,10 +138,10 @@
     double oldDragX = _dragX;
     _dragX += event.dx;
     setState(() {
-      if (!_performance.isAnimating) {
+      if (!_fadePerformance.isAnimating) {
         if (oldDragX.sign != _dragX.sign)
           _position.end = _activeCardDragEndPoint;
-        _performance.progress = _dragX.abs() / (_width * _kDismissCardThreshold);
+        _fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold);
       }
     });
   }
@@ -112,10 +152,10 @@
 
     setState(() {
       _dragUnderway = false;
-      if (_performance.isCompleted)
-        _maybeCallOnDismissed();
-      else if (!_performance.isAnimating)
-        _performance.reverse();
+      if (_fadePerformance.isCompleted)
+        _startResizePerformance();
+      else if (!_fadePerformance.isAnimating)
+        _fadePerformance.reverse();
     });
   }
 
@@ -133,11 +173,19 @@
       _dragUnderway = false;
       _dragX = event.velocityX.sign;
       _position.end = _activeCardDragEndPoint;
-      _performance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale);
+      _fadePerformance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale);
     }
   }
 
+  void _handleSizeChanged(Size newSize) {
+    _size = new Size.copy(newSize);
+    _position.end = _activeCardDragEndPoint;
+  }
+
   Widget build() {
+    if (_resizePerformance != null)
+      return new Container(height: _resizePerformance.variable.value);
+
     Matrix4 transform = new Matrix4.identity();
     transform.translate(_position.value.x, _position.value.y);
     return new Listener(