Sliver Padding (#7628)

diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart
index d579bdf..d09b041 100644
--- a/packages/flutter/lib/rendering.dart
+++ b/packages/flutter/lib/rendering.dart
@@ -47,6 +47,7 @@
 export 'src/rendering/sliver.dart';
 export 'src/rendering/sliver_app_bar.dart';
 export 'src/rendering/sliver_block.dart';
+export 'src/rendering/sliver_padding.dart';
 export 'src/rendering/stack.dart';
 export 'src/rendering/table.dart';
 export 'src/rendering/tweens.dart';
diff --git a/packages/flutter/lib/src/rendering/debug.dart b/packages/flutter/lib/src/rendering/debug.dart
index c815f9c..ab843f6 100644
--- a/packages/flutter/lib/src/rendering/debug.dart
+++ b/packages/flutter/lib/src/rendering/debug.dart
@@ -14,7 +14,8 @@
 const Color _kDebugPaintSpacingColor = const Color(0x90909090);
 const Color _kDebugPaintPaddingColor = const Color(0x900090FF);
 const Color _kDebugPaintPaddingInnerEdgeColor = const Color(0xFF0090FF);
-const Color _kDebugPaintArrowColor = const Color(0xFFFFFF00);
+const Color _kDebugPaintBoxArrowColor = const Color(0xFFFFFF00);
+const Color _kDebugPaintSliverArrowColor = const Color(0xFF33CC33);
 const Color _kDebugPaintAlphabeticBaselineColor = const Color(0xFF00FF00);
 const Color _kDebugPaintIdeographicBaselineColor = const Color(0xFFFFD000);
 const Color _kDebugPaintLayerBordersColor = const Color(0xFFFF9800);
@@ -23,34 +24,37 @@
 const double _kDebugRepaintRainbowHueIncrement = 2.0;
 
 /// Causes each RenderBox to paint a box around its bounds, and some extra
-/// boxes, such as RenderPadding, to draw construction lines.
+/// boxes, such as [RenderPadding], to draw construction lines.
 bool debugPaintSizeEnabled = false;
 
 /// The color to use when painting RenderObject bounds.
 Color debugPaintSizeColor = _kDebugPaintSizeColor;
 
 /// The color to use when painting some boxes that just add space (e.g. an empty
-/// RenderConstrainedBox or RenderPadding).
+/// RenderConstrainedBox or [RenderPadding]).
 ///
 /// Used by, among other methods, [debugPaintPadding], which is called by
 /// [RenderPadding.debugPaintSize] when [debugPaintSizeEnabled] is true.
 Color debugPaintSpacingColor = _kDebugPaintSpacingColor;
 
-/// The color to use when painting RenderPadding edges.
+/// The color to use when painting [RenderPadding] edges.
 ///
 /// Used by, among other methods, [debugPaintPadding], which is called by
 /// [RenderPadding.debugPaintSize] when [debugPaintSizeEnabled] is true.
 Color debugPaintPaddingColor = _kDebugPaintPaddingColor;
 
-/// The color to use when painting RenderPadding edges. This color is painted on
+/// The color to use when painting [RenderPadding] edges. This color is painted on
 /// top of [debugPaintPaddingColor].
 ///
 /// Used by, among other methods, [debugPaintPadding], which is called by
 /// [RenderPadding.debugPaintSize] when [debugPaintSizeEnabled] is true.
 Color debugPaintPaddingInnerEdgeColor = _kDebugPaintPaddingInnerEdgeColor;
 
-/// The color to use when painting the arrows used to show RenderPositionedBox alignment.
-Color debugPaintArrowColor = _kDebugPaintArrowColor;
+/// The color to use when painting the arrows used to show [RenderPositionedBox] alignment.
+Color debugPaintBoxArrowColor = _kDebugPaintBoxArrowColor;
+
+/// The color to use when painting the arrows used to show [RenderSliver] alignment.
+Color debugPaintSliverArrowColor = _kDebugPaintSliverArrowColor;
 
 /// Causes each RenderBox to paint a line at each of its baselines.
 bool debugPaintBaselinesEnabled = false;
@@ -166,7 +170,8 @@
         debugPaintSpacingColor != _kDebugPaintSpacingColor ||
         debugPaintPaddingColor != _kDebugPaintPaddingColor ||
         debugPaintPaddingInnerEdgeColor != _kDebugPaintPaddingInnerEdgeColor ||
-        debugPaintArrowColor != _kDebugPaintArrowColor ||
+        debugPaintBoxArrowColor != _kDebugPaintBoxArrowColor ||
+        debugPaintSliverArrowColor != _kDebugPaintSliverArrowColor ||
         debugPaintAlphabeticBaselineColor != _kDebugPaintAlphabeticBaselineColor ||
         debugPaintIdeographicBaselineColor != _kDebugPaintIdeographicBaselineColor ||
         debugPaintLayerBordersColor != _kDebugPaintLayerBordersColor ||
diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart
index 265f365..2630623 100644
--- a/packages/flutter/lib/src/rendering/shifted_box.dart
+++ b/packages/flutter/lib/src/rendering/shifted_box.dart
@@ -320,7 +320,7 @@
         paint = new Paint()
           ..style = PaintingStyle.stroke
           ..strokeWidth = 1.0
-          ..color = debugPaintArrowColor;
+          ..color = debugPaintBoxArrowColor;
         path = new Path();
         final BoxParentData childParentData = child.parentData;
         if (childParentData.offset.dy > 0.0) {
diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart
index 99fb57c..15d7708 100644
--- a/packages/flutter/lib/src/rendering/sliver.dart
+++ b/packages/flutter/lib/src/rendering/sliver.dart
@@ -329,7 +329,7 @@
       return false;
     final SliverConstraints typedOther = other;
     assert(typedOther.debugAssertIsValid());
-    return axis == typedOther.axis &&
+    return axisDirection == typedOther.axisDirection &&
            growthDirection == typedOther.growthDirection &&
            scrollOffset == typedOther.scrollOffset &&
            overlap == typedOther.overlap &&
@@ -339,7 +339,7 @@
 
   @override
   int get hashCode {
-    return hashValues(axis, growthDirection, scrollOffset, overlap, remainingPaintExtent, crossAxisExtent);
+    return hashValues(axisDirection, growthDirection, scrollOffset, overlap, remainingPaintExtent, crossAxisExtent);
   }
 
   @override
@@ -684,8 +684,6 @@
   /// [RenderViewport2.axisDirection], so values will typically be positive.
   double get centerOffsetAdjustment => 0.0;
 
-  void didScroll(double delta, Point focus) { }
-
   /// Determines the set of render objects located at the given position.
   ///
   /// Returns true if the given point is contained in this render object or one
@@ -820,10 +818,30 @@
     });
   }
 
+  /// This returns a [Size] with dimensions relative to the leading edge of the
+  /// sliver, specifically the same offset that is given to the [paint] method.
+  /// This means that the dimensions may be negative.
+  @protected
+  Size getAbsoluteSizeRelativeToOrigin() {
+    assert(geometry != null);
+    assert(!needsLayout);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return new Size(constraints.crossAxisExtent, -geometry.paintExtent);
+      case AxisDirection.right:
+        return new Size(geometry.paintExtent, constraints.crossAxisExtent);
+      case AxisDirection.down:
+        return new Size(constraints.crossAxisExtent, geometry.paintExtent);
+      case AxisDirection.left:
+        return new Size(-geometry.paintExtent, constraints.crossAxisExtent);
+    }
+    return null;
+  }
+
   void _debugDrawArrow(Canvas canvas, Paint paint, Point p0, Point p1, GrowthDirection direction) {
     assert(() {
       if (p0 == p1)
-        return;
+        return true;
       assert(p0.x == p1.x || p0.y == p1.y); // must be axis-aligned
       final double d = (p1 - p0).distance * 0.2;
       Point temp;
@@ -853,6 +871,7 @@
           ..lineTo(p1.x - dx2, p1.y - dy2),
         paint
       );
+      return true;
     });
   }
 
@@ -862,7 +881,7 @@
       if (debugPaintSizeEnabled) {
         final double strokeWidth = math.min(4.0, geometry.paintExtent / 30.0);
         final Paint paint = new Paint()
-          ..color = const Color(0xFF33CC33)
+          ..color = debugPaintSliverArrowColor
           ..strokeWidth = strokeWidth
           ..style = PaintingStyle.stroke
           ..maskFilter = new MaskFilter.blur(BlurStyle.solid, strokeWidth);
diff --git a/packages/flutter/lib/src/rendering/sliver_padding.dart b/packages/flutter/lib/src/rendering/sliver_padding.dart
new file mode 100644
index 0000000..a85b1d6
--- /dev/null
+++ b/packages/flutter/lib/src/rendering/sliver_padding.dart
@@ -0,0 +1,312 @@
+// Copyright 2016 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 'dart:math' as math;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:meta/meta.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'binding.dart';
+import 'debug.dart';
+import 'object.dart';
+import 'sliver.dart';
+
+/// Inset a [RenderSliver], applying padding on each side.
+///
+/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
+/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
+/// passed on to the child.
+///
+/// Applying this to anything but the most mundane sliver is likely to have
+/// undesired effects. For example, wrapping a [RenderSliverPinnedAppBar] will
+/// cause the app bar to overlap earlier slivers (contrary to the normal
+/// behavior of pinned app bars), and while the app bar is pinned, the padding
+/// will scroll away.
+class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
+  /// Creates a render object that insets its child in a viewport.
+  ///
+  /// The [padding] argument must not be null and must have non-negative insets.
+  RenderSliverPadding({
+    EdgeInsets padding,
+    RenderSliver child,
+  }) : _padding = padding {
+    assert(padding != null);
+    assert(padding.isNonNegative);
+    this.child = child;
+  }
+
+  /// The amount to pad the child in each dimension.
+  EdgeInsets get padding => _padding;
+  EdgeInsets _padding;
+  set padding (EdgeInsets value) {
+    assert(value != null);
+    assert(value.isNonNegative);
+    if (_padding == value)
+      return;
+    _padding = value;
+    markNeedsLayout();
+  }
+
+  /// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get beforePadding {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return padding.bottom;
+      case AxisDirection.right:
+        return padding.left;
+      case AxisDirection.down:
+        return padding.top;
+      case AxisDirection.left:
+        return padding.right;
+    }
+    return null;
+  }
+
+  /// The padding in the scroll direction on the side furthest from the 0.0 scroll offset.
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get afterPadding {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return padding.top;
+      case AxisDirection.right:
+        return padding.right;
+      case AxisDirection.down:
+        return padding.bottom;
+      case AxisDirection.left:
+        return padding.left;
+    }
+    return null;
+  }
+
+  /// The padding in the cross-axis direction on the side to the right when
+  /// facing away from the zero scroll offset in the scroll axis direction. (In
+  /// other words, for a vertical downwards-growing list, the padding on the
+  /// left.)
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get startPadding {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return padding.right;
+      case AxisDirection.right:
+        return padding.top;
+      case AxisDirection.down:
+        return padding.left;
+      case AxisDirection.left:
+        return padding.bottom;
+    }
+    return null;
+  }
+
+  /// The padding in the cross-axis direction on the side to the left when
+  /// facing away from the zero scroll offset in the scroll axis direction. (In
+  /// other words, for a vertical downwards-growing list, the padding on the
+  /// right.)
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get endPadding {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return padding.left;
+      case AxisDirection.right:
+        return padding.bottom;
+      case AxisDirection.down:
+        return padding.right;
+      case AxisDirection.left:
+        return padding.top;
+    }
+    return null;
+  }
+
+  /// The total padding in the [constraints.axisDirection]. (In other words, for
+  /// a vertical downwards-growing list, the sum of the padding on the top and
+  /// bottom.)
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get mainAxisPadding {
+    assert(constraints != null);
+    assert(constraints.axis != null);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        return padding.left + padding.right;
+      case Axis.vertical:
+        return padding.top + padding.bottom;
+    }
+    return null;
+  }
+
+  /// The total padding in the cross-axis direction. (In other words, for a
+  /// vertical downwards-growing list, the sum of the padding on the left and
+  /// right.)
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get crossAxisPadding {
+    assert(constraints != null);
+    assert(constraints.axis != null);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        return padding.top + padding.bottom;
+      case Axis.vertical:
+        return padding.left + padding.right;
+    }
+    return null;
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverPhysicalParentData)
+      child.parentData = new SliverPhysicalParentData();
+  }
+
+  @override
+  void performLayout() {
+    final double beforePadding = this.beforePadding;
+    final double afterPadding = this.afterPadding;
+    final double mainAxisPadding = this.mainAxisPadding;
+    final double crossAxisPadding = this.crossAxisPadding;
+    if (child == null) {
+      geometry = new SliverGeometry(
+        scrollExtent: mainAxisPadding,
+        paintExtent: math.min(mainAxisPadding, constraints.remainingPaintExtent),
+        maxPaintExtent: mainAxisPadding,
+      );
+      return;
+    }
+    child.layout(
+      constraints.copyWith(
+        scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
+        overlap: 0.0,
+        remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
+        crossAxisExtent: constraints.crossAxisExtent - crossAxisPadding,
+      ),
+      parentUsesSize: true,
+    );
+    final SliverGeometry childLayoutGeometry = child.geometry;
+    final double beforePaddingPaintExtent = calculatePaintOffset(
+      constraints,
+      from: 0.0,
+      to: beforePadding,
+    );
+    final double afterPaddingPaintExtent = calculatePaintOffset(
+      constraints,
+      from: beforePadding + childLayoutGeometry.scrollExtent,
+      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
+    );
+    final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
+    geometry = new SliverGeometry(
+      scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
+      paintExtent: math.min(
+        beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
+        constraints.remainingPaintExtent,
+      ),
+      layoutExtent: math.min(
+        mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent,
+        constraints.remainingPaintExtent,
+      ),
+      maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
+      hitTestExtent: math.max(
+        mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
+        beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
+      ),
+    );
+
+    final SliverPhysicalParentData childParentData = child.parentData;
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        childParentData.paintOffset = new Offset(padding.left, calculatePaintOffset(constraints, from: padding.bottom + childLayoutGeometry.scrollExtent, to: padding.bottom + childLayoutGeometry.scrollExtent + padding.top));
+        break;
+      case AxisDirection.right:
+        childParentData.paintOffset = new Offset(calculatePaintOffset(constraints, from: 0.0, to: padding.left), padding.top);
+        break;
+      case AxisDirection.down:
+        childParentData.paintOffset = new Offset(padding.left, calculatePaintOffset(constraints, from: 0.0, to: padding.top));
+        break;
+      case AxisDirection.left:
+        childParentData.paintOffset = new Offset(calculatePaintOffset(constraints, from: padding.right + childLayoutGeometry.scrollExtent, to: padding.right + childLayoutGeometry.scrollExtent + padding.left), padding.top);
+        break;
+    }
+    assert(childParentData.paintOffset != null);
+    assert(beforePadding == this.beforePadding);
+    assert(afterPadding == this.afterPadding);
+    assert(mainAxisPadding == this.mainAxisPadding);
+    assert(crossAxisPadding == this.crossAxisPadding);
+  }
+
+  @override
+  bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
+    if (child.geometry.hitTestExtent > 0.0)
+      return child.hitTest(result, mainAxisPosition: mainAxisPosition - childPosition(child), crossAxisPosition: crossAxisPosition - startPadding);
+    return false;
+  }
+
+  @override
+  double childPosition(RenderSliver child) {
+    assert(child != null);
+    assert(child == this.child);
+    return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    assert(child == this.child);
+    final SliverPhysicalParentData childParentData = child.parentData;
+    childParentData.applyPaintTransform(transform);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null && child.geometry.visible) {
+      final SliverPhysicalParentData childParentData = child.parentData;
+      context.paintChild(child, offset + childParentData.paintOffset);
+    }
+  }
+
+  @override
+  void debugPaint(PaintingContext context, Offset offset) {
+    super.debugPaint(context, offset);
+    assert(() {
+      if (debugPaintSizeEnabled) {
+        final Size parentSize = getAbsoluteSizeRelativeToOrigin();
+        final Rect outerRect = offset & parentSize;
+        Size childSize;
+        Rect innerRect;
+        if (child != null) {
+          childSize = child.getAbsoluteSizeRelativeToOrigin();
+          final SliverPhysicalParentData childParentData = child.parentData;
+          innerRect = (offset + childParentData.paintOffset) & childSize;
+          assert(outerRect.contains(innerRect.topLeft));
+          assert(outerRect.contains(innerRect.bottomRight));
+        }
+        debugPaintPadding(context.canvas, outerRect, innerRect);
+      }
+      return true;
+    });
+  }
+}
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index bdbeec9..306c551 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -1439,6 +1439,45 @@
 }
 
 
+// SLIVERS
+
+class SliverToBoxAdapter extends SingleChildRenderObjectWidget {
+  SliverToBoxAdapter({
+    Key key,
+    Widget child,
+  }) : super(key: key, child: child);
+
+  @override
+  RenderSliverToBoxAdapter createRenderObject(BuildContext context) => new RenderSliverToBoxAdapter();
+}
+
+class SliverPadding extends SingleChildRenderObjectWidget {
+  SliverPadding({
+    Key key,
+    @required this.padding,
+    Widget child,
+  }) : super(key: key, child: child) {
+    assert(padding != null);
+  }
+
+  final EdgeInsets padding;
+
+  @override
+  RenderSliverPadding createRenderObject(BuildContext context) => new RenderSliverPadding(padding: padding);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverPadding renderObject) {
+    renderObject.padding = padding;
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('padding: $padding');
+  }
+}
+
+
 // LAYOUT NODES
 
 /// A widget that uses the block layout algorithm for its children.
@@ -2703,16 +2742,6 @@
   }
 }
 
-class SliverToBoxAdapter extends SingleChildRenderObjectWidget {
-  SliverToBoxAdapter({
-    Key key,
-    Widget child,
-  }) : super(key: key, child: child);
-
-  @override
-  RenderSliverToBoxAdapter createRenderObject(BuildContext context) => new RenderSliverToBoxAdapter();
-}
-
 
 // EVENT HANDLING
 
diff --git a/packages/flutter/test/rendering/debug_test.dart b/packages/flutter/test/rendering/debug_test.dart
index f810ab9..56359c2 100644
--- a/packages/flutter/test/rendering/debug_test.dart
+++ b/packages/flutter/test/rendering/debug_test.dart
@@ -6,6 +6,7 @@
 import 'package:test/test.dart';
 import 'package:vector_math/vector_math_64.dart';
 
+import 'rendering_tester.dart';
 import 'mock_canvas.dart';
 
 void main() {
@@ -31,4 +32,53 @@
       debugPaintPadding(canvas, new Rect.fromLTRB(10.0, 10.0, 20.0, 20.0), new Rect.fromLTRB(15.0, 15.0, 15.0, 15.0));
     }, paints..rect(rect: new Rect.fromLTRB(10.0, 10.0, 20.0, 20.0), color: debugPaintSpacingColor));
   });
+
+  test('debugPaintPadding from render objects', () {
+    debugPaintSizeEnabled = true;
+    RenderSliver s;
+    RenderBox b;
+    RenderViewport2 root = new RenderViewport2(
+      offset: new ViewportOffset.zero(),
+      children: <RenderSliver>[
+        s = new RenderSliverPadding(
+          padding: new EdgeInsets.all(10.0),
+          child: new RenderSliverToBoxAdapter(
+            child: b = new RenderPadding(
+              padding: new EdgeInsets.all(10.0),
+            ),
+          ),
+        ),
+      ],
+    );
+    layout(root);
+    expect(b.debugPaint, paints..rect(color: debugPaintSizeColor)..rect(color: debugPaintSpacingColor));
+    expect(b.debugPaint, isNot(paints..path()));
+    expect(s.debugPaint, paints..circle(hasMaskFilter: true)..line(hasMaskFilter: true)..path(hasMaskFilter: true)..path(hasMaskFilter: true)
+                               ..path(color: debugPaintPaddingColor)..path(color: debugPaintPaddingInnerEdgeColor));
+    expect(s.debugPaint, isNot(paints..rect()));
+    debugPaintSizeEnabled = false;
+  });
+
+  test('debugPaintPadding from render objects', () {
+    debugPaintSizeEnabled = true;
+    RenderSliver s;
+    RenderBox b = new RenderPadding(
+      padding: new EdgeInsets.all(10.0),
+      child: new RenderViewport2(
+        offset: new ViewportOffset.zero(),
+        children: <RenderSliver>[
+          s = new RenderSliverPadding(
+            padding: new EdgeInsets.all(10.0),
+          ),
+        ],
+      ),
+    );
+    layout(b);
+    expect(s.debugPaint, paints..rect(color: debugPaintSpacingColor));
+    expect(s.debugPaint, isNot(paints..circle(hasMaskFilter: true)..line(hasMaskFilter: true)..path(hasMaskFilter: true)..path(hasMaskFilter: true)
+                                     ..path(color: debugPaintPaddingColor)..path(color: debugPaintPaddingInnerEdgeColor)));
+    expect(b.debugPaint, paints..rect(color: debugPaintSizeColor)..path(color: debugPaintPaddingColor)..path(color: debugPaintPaddingInnerEdgeColor));
+    expect(b.debugPaint, isNot(paints..rect(color: debugPaintSpacingColor)));
+    debugPaintSizeEnabled = false;
+  });
 }
diff --git a/packages/flutter/test/rendering/mock_canvas.dart b/packages/flutter/test/rendering/mock_canvas.dart
index 503b190..f3c512c 100644
--- a/packages/flutter/test/rendering/mock_canvas.dart
+++ b/packages/flutter/test/rendering/mock_canvas.dart
@@ -158,6 +158,18 @@
   /// [Canvas.drawPath] call are ignored.
   void path({ Color color, bool hasMaskFilter, PaintingStyle style });
 
+  /// Indicates that a line is expected next.
+  ///
+  /// The next line is examined. Any arguments that are passed to this method
+  /// are compared to the actual [Canvas.drawLine] call's `paint` argument, and
+  /// any mismatches result in failure.
+  ///
+  /// If no call to [Canvas.drawLine] was made, then this results in failure.
+  ///
+  /// Any calls made between the last matched call (if any) and the
+  /// [Canvas.drawLine] call are ignored.
+  void line({ Color color, bool hasMaskFilter, PaintingStyle style });
+
   /// Provides a custom matcher.
   ///
   /// Each method call after the last matched call (if any) will be passed to
@@ -236,6 +248,11 @@
   }
 
   @override
+  void line({ Color color, bool hasMaskFilter, PaintingStyle style }) {
+    _predicates.add(new _LinePaintPredicate(color: color, hasMaskFilter: hasMaskFilter, style: style));
+  }
+
+  @override
   void something(PaintPatternPredicate predicate) {
     _predicates.add(new _SomethingPaintPredicate(predicate));
   }
@@ -573,6 +590,13 @@
   );
 }
 
+// TODO(ianh): add arguments to test the points, length, angle, that kind of thing
+class _LinePaintPredicate extends _DrawCommandPaintPredicate {
+  _LinePaintPredicate({ Color color, bool hasMaskFilter, PaintingStyle style }) : super(
+    #drawLine, 'a line', 3, 2, color: color, hasMaskFilter: hasMaskFilter, style: style
+  );
+}
+
 class _SomethingPaintPredicate extends _PaintPredicate {
   _SomethingPaintPredicate(this.predicate);
 
diff --git a/packages/flutter/test/widgets/slivers_block_global_key_test.dart b/packages/flutter/test/widgets/slivers_block_global_key_test.dart
index a2b63b8..871a1fe 100644
--- a/packages/flutter/test/widgets/slivers_block_global_key_test.dart
+++ b/packages/flutter/test/widgets/slivers_block_global_key_test.dart
@@ -37,13 +37,13 @@
 }
 
 void verify(WidgetTester tester, List<Point> answerKey, String text) {
-  List<Point> testAnswers = tester.renderObjectList/*<RenderBox>*/(find.byType(SizedBox)).map/*<Point>*/(
+  List<Point> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Point>(
     (RenderBox target) => target.localToGlobal(const Point(0.0, 0.0))
   ).toList();
   expect(testAnswers, equals(answerKey));
   final String foundText =
-    tester.widgetList/*<Text>*/(find.byType(Text))
-    .map/*<String>*/((Text widget) => widget.data)
+    tester.widgetList<Text>(find.byType(Text))
+    .map<String>((Text widget) => widget.data)
     .reduce((String value, String element) => value + element);
   expect(foundText, equals(text));
 }
diff --git a/packages/flutter/test/widgets/slivers_block_test.dart b/packages/flutter/test/widgets/slivers_block_test.dart
index d6355a6..a379249 100644
--- a/packages/flutter/test/widgets/slivers_block_test.dart
+++ b/packages/flutter/test/widgets/slivers_block_test.dart
@@ -24,13 +24,13 @@
 }
 
 void verify(WidgetTester tester, List<Point> answerKey, String text) {
-  List<Point> testAnswers = tester.renderObjectList/*<RenderBox>*/(find.byType(SizedBox)).map/*<Point>*/(
+  List<Point> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Point>(
     (RenderBox target) => target.localToGlobal(const Point(0.0, 0.0))
   ).toList();
   expect(testAnswers, equals(answerKey));
   final String foundText =
-    tester.widgetList/*<Text>*/(find.byType(Text))
-    .map/*<String>*/((Text widget) => widget.data)
+    tester.widgetList<Text>(find.byType(Text))
+    .map<String>((Text widget) => widget.data)
     .reduce((String value, String element) => value + element);
   expect(foundText, equals(text));
 }
@@ -38,7 +38,7 @@
 void main() {
   testWidgets('Viewport2+SliverBlock basic test', (WidgetTester tester) async {
     await test(tester, 0.0);
-    expect(tester.renderObject/*<RenderBox>*/(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
+    expect(tester.renderObject<RenderBox>(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
     verify(tester, <Point>[
       const Point(0.0, 0.0),
       const Point(0.0, 400.0),
diff --git a/packages/flutter/test/widgets/slivers_padding_test.dart b/packages/flutter/test/widgets/slivers_padding_test.dart
new file mode 100644
index 0000000..844357e
--- /dev/null
+++ b/packages/flutter/test/widgets/slivers_padding_test.dart
@@ -0,0 +1,240 @@
+// Copyright 2016 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:flutter_test/flutter_test.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+
+Future<Null> test(WidgetTester tester, double offset, EdgeInsets padding, AxisDirection axisDirection) {
+  return tester.pumpWidget(new Viewport2(
+    offset: new ViewportOffset.fixed(offset),
+    axisDirection: axisDirection,
+    children: <Widget>[
+      new SliverToBoxAdapter(child: new SizedBox(width: 400.0, height: 400.0, child: new Text('before'))),
+      new SliverPadding(
+        padding: padding,
+        child: new SliverToBoxAdapter(child: new SizedBox(width: 400.0, height: 400.0, child: new Text('padded'))),
+      ),
+      new SliverToBoxAdapter(child: new SizedBox(width: 400.0, height: 400.0, child: new Text('after'))),
+    ],
+  ));
+}
+
+void verify(WidgetTester tester, List<Rect> answerKey) {
+  List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Rect>(
+    (RenderBox target) {
+      Point topLeft = target.localToGlobal(Point.origin);
+      Point bottomRight = target.localToGlobal(target.size.bottomRight(Point.origin));
+      return new Rect.fromPoints(topLeft, bottomRight);
+    }
+  ).toList();
+  expect(testAnswers, equals(answerKey));
+}
+
+void main() {
+  testWidgets('Viewport2+SliverPadding basic test', (WidgetTester tester) async {
+    EdgeInsets padding = const EdgeInsets.fromLTRB(25.0, 20.0, 15.0, 35.0);
+    await test(tester, 0.0, padding, AxisDirection.down);
+    expect(tester.renderObject<RenderBox>(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, 0.0, 800.0, 400.0),
+      new Rect.fromLTWH(25.0, 420.0, 760.0, 400.0),
+      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+    ]);
+
+    await test(tester, 200.0, padding, AxisDirection.down);
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, -200.0, 800.0, 400.0),
+      new Rect.fromLTWH(25.0, 220.0, 760.0, 400.0),
+      new Rect.fromLTWH(0.0, 600.0, 800.0, 400.0),
+    ]);
+
+    await test(tester, 390.0, padding, AxisDirection.down);
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, -390.0, 800.0, 400.0),
+      new Rect.fromLTWH(25.0, 30.0, 760.0, 400.0),
+      new Rect.fromLTWH(0.0, 465.0, 800.0, 400.0),
+    ]);
+
+    await test(tester, 490.0, padding, AxisDirection.down);
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, -490.0, 800.0, 400.0),
+      new Rect.fromLTWH(25.0, -70.0, 760.0, 400.0),
+      new Rect.fromLTWH(0.0, 365.0, 800.0, 400.0),
+    ]);
+
+    await test(tester, 10000.0, padding, AxisDirection.down);
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, -10000.0, 800.0, 400.0),
+      new Rect.fromLTWH(25.0, -9580.0, 760.0, 400.0),
+      new Rect.fromLTWH(0.0, -9145.0, 800.0, 400.0),
+    ]);
+  });
+
+  testWidgets('Viewport2+SliverPadding hit testing', (WidgetTester tester) async {
+    EdgeInsets padding = const EdgeInsets.all(30.0);
+    await test(tester, 350.0, padding, AxisDirection.down);
+    expect(tester.renderObject<RenderBox>(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, -350.0, 800.0, 400.0),
+      new Rect.fromLTWH(30.0, 80.0, 740.0, 400.0),
+      new Rect.fromLTWH(0.0, 510.0, 800.0, 400.0),
+    ]);
+    HitTestResult result;
+    result = tester.hitTestOnBinding(const Point(10.0, 10.0));
+    expect(result.path.first.target, tester.firstRenderObject<RenderObject>(find.byType(Text)));
+    result = tester.hitTestOnBinding(const Point(10.0, 60.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(100.0, 100.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).skip(1).first);
+    result = tester.hitTestOnBinding(const Point(100.0, 490.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(10.0, 520.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).last);
+  });
+
+  testWidgets('Viewport2+SliverPadding hit testing up', (WidgetTester tester) async {
+    EdgeInsets padding = const EdgeInsets.all(30.0);
+    await test(tester, 350.0, padding, AxisDirection.up);
+    expect(tester.renderObject<RenderBox>(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(0.0, 600.0+350.0-400.0, 800.0, 400.0),
+      new Rect.fromLTWH(30.0, 600.0-80.0-400.0, 740.0, 400.0),
+      new Rect.fromLTWH(0.0, 600.0-510.0-400.0, 800.0, 400.0),
+    ]);
+    HitTestResult result;
+    result = tester.hitTestOnBinding(const Point(10.0, 600.0-10.0));
+    expect(result.path.first.target, tester.firstRenderObject<RenderObject>(find.byType(Text)));
+    result = tester.hitTestOnBinding(const Point(10.0, 600.0-60.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(100.0, 600.0-100.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).skip(1).first);
+    result = tester.hitTestOnBinding(const Point(100.0, 600.0-490.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(10.0, 600.0-520.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).last);
+  });
+
+  testWidgets('Viewport2+SliverPadding hit testing left', (WidgetTester tester) async {
+    EdgeInsets padding = const EdgeInsets.all(30.0);
+    await test(tester, 350.0, padding, AxisDirection.left);
+    expect(tester.renderObject<RenderBox>(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(800.0+350.0-400.0, 0.0, 400.0, 600.0),
+      new Rect.fromLTWH(800.0-80.0-400.0, 30.0, 400.0, 540.0),
+      new Rect.fromLTWH(800.0-510.0-400.0, 0.0, 400.0, 600.0),
+    ]);
+    HitTestResult result;
+    result = tester.hitTestOnBinding(const Point(800.0-10.0, 10.0));
+    expect(result.path.first.target, tester.firstRenderObject<RenderObject>(find.byType(Text)));
+    result = tester.hitTestOnBinding(const Point(800.0-60.0, 10.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(800.0-100.0, 100.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).skip(1).first);
+    result = tester.hitTestOnBinding(const Point(800.0-490.0, 100.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(800.0-520.0, 10.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).last);
+  });
+
+  testWidgets('Viewport2+SliverPadding hit testing right', (WidgetTester tester) async {
+    EdgeInsets padding = const EdgeInsets.all(30.0);
+    await test(tester, 350.0, padding, AxisDirection.right);
+    expect(tester.renderObject<RenderBox>(find.byType(Viewport2)).size, equals(const Size(800.0, 600.0)));
+    verify(tester, <Rect>[
+      new Rect.fromLTWH(-350.0, 0.0, 400.0, 600.0),
+      new Rect.fromLTWH(80.0, 30.0, 400.0, 540.0),
+      new Rect.fromLTWH(510.0, 0.0, 400.0, 600.0),
+    ]);
+    HitTestResult result;
+    result = tester.hitTestOnBinding(const Point(10.0, 10.0));
+    expect(result.path.first.target, tester.firstRenderObject<RenderObject>(find.byType(Text)));
+    result = tester.hitTestOnBinding(const Point(60.0, 10.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(100.0, 100.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).skip(1).first);
+    result = tester.hitTestOnBinding(const Point(490.0, 100.0));
+    expect(result.path.first.target, new isInstanceOf<RenderView>());
+    result = tester.hitTestOnBinding(const Point(520.0, 10.0));
+    expect(result.path.first.target, tester.renderObjectList<RenderObject>(find.byType(Text)).last);
+  });
+
+  testWidgets('Viewport2+SliverPadding no child', (WidgetTester tester) async {
+    await tester.pumpWidget(new Viewport2(
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.all(100.0)),
+        new SliverToBoxAdapter(child: new SizedBox(width: 400.0, height: 400.0, child: new Text('x'))),
+      ],
+    ));
+    expect(tester.renderObject<RenderBox>(find.text('x')).localToGlobal(Point.origin), const Point(0.0, 200.0));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 100.0);
+  });
+
+  testWidgets('Viewport2+SliverPadding changing padding', (WidgetTester tester) async {
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.left,
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(90.0, 1.0, 110.0, 2.0)),
+        new SliverToBoxAdapter(child: new SizedBox(width: 201.0, child: new Text('x'))),
+      ],
+    ));
+    expect(tester.renderObject<RenderBox>(find.text('x')).localToGlobal(Point.origin), const Point(399.0, 0.0));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 1.0);
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.left,
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(110.0, 1.0, 80.0, 2.0)),
+        new SliverToBoxAdapter(child: new SizedBox(width: 201.0, child: new Text('x'))),
+      ],
+    ));
+    expect(tester.renderObject<RenderBox>(find.text('x')).localToGlobal(Point.origin), const Point(409.0, 0.0));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 1.0);
+  });
+
+  testWidgets('Viewport2+SliverPadding changing direction', (WidgetTester tester) async {
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.up,
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(1.0, 2.0, 4.0, 8.0)),
+      ],
+    ));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 1.0);
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.down,
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(1.0, 2.0, 4.0, 8.0)),
+      ],
+    ));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 4.0);
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.right,
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(1.0, 2.0, 4.0, 8.0)),
+      ],
+    ));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 8.0);
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.left,
+      offset: new ViewportOffset.fixed(0.0),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(1.0, 2.0, 4.0, 8.0)),
+      ],
+    ));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 2.0);
+    await tester.pumpWidget(new Viewport2(
+      axisDirection: AxisDirection.left,
+      offset: new ViewportOffset.fixed(99999.9),
+      children: <Widget>[
+        new SliverPadding(padding: new EdgeInsets.fromLTRB(1.0, 2.0, 4.0, 8.0)),
+      ],
+    ));
+    expect(tester.renderObject<RenderSliverPadding>(find.byType(SliverPadding)).endPadding, 2.0);
+  });
+}