Improve compositing strategy for Shrine (#5014)

This patch includes a number of improvements:

 * Material page routes now put a repaint boundary inside their transition so
   they don't repaint during the transition.
 * Heroes that are on a quest now get a repaint boundary so we repaint them
   individually.
 * I've hoisted the transparent material for the product items up in the widget
   tree, which doesn't affect performance but makes the ink splashes reach the
   edge of the product cards.
 * I've changed the repaint rainbow visualization to make it easier to see
   what's going on.
diff --git a/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart b/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
index 901a29a..13158b3 100644
--- a/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
+++ b/examples/flutter_gallery/lib/demo/shrine/shrine_home.dart
@@ -258,37 +258,37 @@
   @override
   Widget build(BuildContext context) {
     return new Card(
-      child: new Column(
+      child: new Stack(
         children: <Widget>[
-          new Align(
-            alignment: FractionalOffset.centerRight,
-            child: new ProductPriceItem(product: product)
-          ),
-          new Container(
-            width: 144.0,
-            height: 144.0,
-            padding: const EdgeInsets.symmetric(horizontal: 8.0),
-            child: new Stack(
-              children: <Widget>[
-                new Hero(
-                  tag: productHeroTag,
-                  key: new ObjectKey(product),
-                  child: new Image(
-                    image: new AssetImage(product.imageAsset),
-                    fit: ImageFit.contain
+          new Column(
+            children: <Widget>[
+              new Align(
+                alignment: FractionalOffset.centerRight,
+                child: new ProductPriceItem(product: product)
+              ),
+              new Container(
+                width: 144.0,
+                height: 144.0,
+                padding: const EdgeInsets.symmetric(horizontal: 8.0),
+                child: new Hero(
+                    tag: productHeroTag,
+                    key: new ObjectKey(product),
+                    child: new Image(
+                      image: new AssetImage(product.imageAsset),
+                      fit: ImageFit.contain
+                    )
                   )
                 ),
-                new Material(
-                  color: Theme.of(context).canvasColor.withAlpha(0x00),
-                  child: new InkWell(onTap: onPressed)
-                ),
-              ]
-            )
+              new Padding(
+                padding: const EdgeInsets.symmetric(horizontal: 8.0),
+                child: new VendorItem(vendor: product.vendor)
+              )
+            ]
           ),
-          new Padding(
-            padding: const EdgeInsets.symmetric(horizontal: 8.0),
-            child: new VendorItem(vendor: product.vendor)
-          )
+          new Material(
+            type: MaterialType.transparency,
+            child: new InkWell(onTap: onPressed)
+          ),
         ]
       )
     );
diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index 5dcd985..7b2a45d 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -173,8 +173,13 @@
     if (!_isRecording)
       return;
     assert(() {
-      if (debugRepaintRainbowEnabled)
-        canvas.drawRect(_paintBounds, new Paint()..color = debugCurrentRepaintColor.toColor());
+      if (debugRepaintRainbowEnabled) {
+        Paint paint = new Paint()
+          ..style = PaintingStyle.stroke
+          ..strokeWidth = 6.0
+          ..color = debugCurrentRepaintColor.toColor();
+        canvas.drawRect(_paintBounds.deflate(3.0), paint);
+      }
       if (debugPaintLayerBordersEnabled) {
         Paint paint = new Paint()
           ..style = PaintingStyle.stroke
diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index a64c7f8..4a86eed 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -272,7 +272,7 @@
       size: animationArea.size,
       child: new RotationTransition(
         turns: currentTurns.animate(animation),
-        child: new KeyedSubtree(
+        child: new RepaintBoundary(
           key: key,
           child: child
         )
diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart
index d73f4da..c5bb5c2 100644
--- a/packages/flutter/lib/src/widgets/routes.dart
+++ b/packages/flutter/lib/src/widgets/routes.dart
@@ -426,15 +426,14 @@
           context,
           config.route.animation,
           config.route.forwardAnimation,
-          contents
+          new RepaintBoundary(child: contents)
         )
       );
     }
-    contents = new Focus(
+    return new Focus(
       key: config.route.focusKey,
-      child: new RepaintBoundary(child: contents)
+      child: contents
     );
-    return contents;
   }
 }