Groundwork for heroes transition in Stocks app

Identify specific parts of a Stock row with a Global Key that can be
regenerated later, and pass that key back to event handlers so they can
use them to do the transition.
diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart
index 0fc8f4a..523b668 100644
--- a/examples/stocks/lib/stock_home.dart
+++ b/examples/stocks/lib/stock_home.dart
@@ -186,13 +186,13 @@
   Widget buildStockList(BuildContext context, Iterable<Stock> stocks) {
     return new StockList(
       stocks: stocks.toList(),
-      onAction: (Stock stock) {
+      onAction: (Stock stock, GlobalKey row, GlobalKey arrowKey, GlobalKey symbolKey, GlobalKey priceKey) {
         setState(() {
           stock.percentChange = 100.0 * (1.0 / stock.lastSale);
           stock.lastSale += 1.0;
         });
       },
-      onOpen: (Stock stock) {
+      onOpen: (Stock stock, GlobalKey row, GlobalKey arrowKey, GlobalKey symbolKey, GlobalKey priceKey) {
         config.navigator.pushNamed('/stock/${stock.symbol}');
       }
     );
diff --git a/examples/stocks/lib/stock_list.dart b/examples/stocks/lib/stock_list.dart
index 4b48f70..54c55c0 100644
--- a/examples/stocks/lib/stock_list.dart
+++ b/examples/stocks/lib/stock_list.dart
@@ -4,14 +4,12 @@
 
 part of stocks;
 
-typedef void StockActionListener(Stock stock);
-
 class StockList extends StatelessComponent {
-  StockList({ Key key, this.stocks, this.onAction, this.onOpen }) : super(key: key);
+  StockList({ Key key, this.stocks, this.onOpen, this.onAction }) : super(key: key);
 
   final List<Stock> stocks;
-  final StockActionListener onAction;
-  final StockActionListener onOpen;
+  final StockRowActionCallback onOpen;
+  final StockRowActionCallback onAction;
 
   Widget build(BuildContext context) {
     return new Material(
@@ -22,8 +20,8 @@
         itemBuilder: (BuildContext context, Stock stock) {
           return new StockRow(
             stock: stock,
-            onPressed: () { onAction(stock); },
-            onLongPressed: () { onOpen(stock); }
+            onPressed: onOpen,
+            onLongPressed: onAction
           );
         }
       )
diff --git a/examples/stocks/lib/stock_row.dart b/examples/stocks/lib/stock_row.dart
index 4b8f47a..c985f82 100644
--- a/examples/stocks/lib/stock_row.dart
+++ b/examples/stocks/lib/stock_row.dart
@@ -4,47 +4,60 @@
 
 part of stocks;
 
+enum StockRowPartKind { arrow, symbol, price }
+
+class StockRowPartGlobalKey extends GlobalKey {
+  const StockRowPartGlobalKey(this.stock, this.part) : super.constructor();
+  final Stock stock;
+  final StockRowPartKind part;
+  String toString() => '[StockRowPartGlobalKey ${stock.symbol}:${part.toString().split(".")[1]})]';
+  bool operator==(other) => other is StockRowPartGlobalKey && identical(other.stock, stock) && identical(other.part, part);
+  int get hashCode => 37 * (37 * (373) + identityHashCode(stock)) + identityHashCode(part);
+}
+
+typedef void StockRowActionCallback(Stock stock, GlobalKey row, GlobalKey arrowKey, GlobalKey symbolKey, GlobalKey priceKey);
+
 class StockRow extends StatelessComponent {
   StockRow({
     Stock stock,
     this.onPressed,
     this.onLongPressed
-  }) : this.stock = stock, super(key: new Key(stock.symbol));
+  }) : this.stock = stock,
+       arrowKey = new StockRowPartGlobalKey(stock, StockRowPartKind.arrow),
+       symbolKey = new StockRowPartGlobalKey(stock, StockRowPartKind.symbol),
+       priceKey = new StockRowPartGlobalKey(stock, StockRowPartKind.price),
+       super(key: new GlobalObjectKey(stock));
 
   final Stock stock;
-  final GestureTapListener onPressed;
-  final GestureLongPressListener onLongPressed;
+  final StockRowActionCallback onPressed;
+  final StockRowActionCallback onLongPressed;
+  final GlobalKey arrowKey;
+  final GlobalKey symbolKey;
+  final GlobalKey priceKey;
 
   static const double kHeight = 79.0;
 
+  GestureTapListener _getTapHandler(StockRowActionCallback callback) {
+    if (callback == null)
+      return null;
+    return () => callback(stock, key, arrowKey, symbolKey, priceKey);
+  }
+
+  GestureLongPressListener _getLongPressHandler(StockRowActionCallback callback) {
+    if (callback == null)
+      return null;
+    return () => callback(stock, key, arrowKey, symbolKey, priceKey);
+  }
+
   Widget build(BuildContext context) {
     String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}";
 
     String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
     if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice;
 
-    List<Widget> children = [
-      new Flexible(
-        child: new Text(stock.symbol),
-        flex: 2
-      ),
-      new Flexible(
-        child: new Text(
-          lastSale,
-          style: const TextStyle(textAlign: TextAlign.right)
-        )
-      ),
-      new Flexible(
-        child: new Text(
-          changeInPrice,
-          style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
-        )
-      )
-    ];
-
     return new GestureDetector(
-      onTap: onPressed,
-      onLongPress: onLongPressed,
+      onTap: _getTapHandler(onPressed),
+      onLongPress: _getLongPressHandler(onLongPressed),
       child: new InkWell(
         child: new Container(
           padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0),
@@ -55,12 +68,33 @@
           ),
           child: new Row([
             new Container(
+              key: arrowKey,
               child: new StockArrow(percentChange: stock.percentChange),
               margin: const EdgeDims.only(right: 5.0)
             ),
             new Flexible(
-              child: new Row(
-                children,
+              child: new Row([
+                  new Flexible(
+                    flex: 2,
+                    child: new Text(
+                      stock.symbol,
+                      key: symbolKey
+                    )
+                  ),
+                  new Flexible(
+                    child: new Text(
+                      lastSale,
+                      style: const TextStyle(textAlign: TextAlign.right),
+                      key: priceKey
+                    )
+                  ),
+                  new Flexible(
+                    child: new Text(
+                      changeInPrice,
+                      style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
+                    )
+                  ),
+                ],
                 alignItems: FlexAlignItems.baseline,
                 textBaseline: DefaultTextStyle.of(context).textBaseline
               )