Make Slider take up whatever space it has. (#10132)

Previously it was (arbitrarily?) set to 144.0 pixels wide.
diff --git a/examples/flutter_gallery/lib/demo/material/slider_demo.dart b/examples/flutter_gallery/lib/demo/material/slider_demo.dart
index 7441d67..4bc9c29 100644
--- a/examples/flutter_gallery/lib/demo/material/slider_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/slider_demo.dart
@@ -19,7 +19,8 @@
   Widget build(BuildContext context) {
     return new Scaffold(
       appBar: new AppBar(title: const Text('Sliders')),
-      body: new Center(
+      body: new Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 40.0),
         child: new Column(
           mainAxisAlignment: MainAxisAlignment.spaceAround,
           children: <Widget>[
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index 584313f..baa9f7e 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -26,13 +26,17 @@
 /// [max] is 50.0 and [divisions] is 5, then the slider can take on the values
 /// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
 ///
+/// The slider will be disabled if [onChanged] is null or if the range given by
+/// [min]..[max] is empty (i.e. if [min] is equal to [max]).
+///
 /// The slider itself does not maintain any state. Instead, when the state of
 /// the slider changes, the widget calls the [onChanged] callback. Most widgets
 /// that use a slider will listen for the [onChanged] callback and rebuild the
 /// slider with a new [value] to update the visual appearance of the slider.
 ///
-/// The slider will be disabled if [onChanged] is null or if the range given by
-/// [min]..[max] is empty (i.e. if [min] is equal to [max]).
+/// By default, a slider will be as wide as possible, centered vertically. When
+/// given unbounded constraints, it will attempt to make the track 144 pixels
+/// wide (with margins on each side) and will shrink-wrap vertically.
 ///
 /// Requires one of its ancestors to be a [Material] widget.
 ///
@@ -237,7 +241,11 @@
 const double _kActiveThumbRadius = 9.0;
 const double _kDisabledThumbRadius = 4.0;
 const double _kReactionRadius = 16.0;
-const double _kTrackWidth = 144.0;
+const double _kPreferredTrackWidth = 144.0;
+const double _kMinimumTrackWidth = _kActiveThumbRadius; // biggest of the thumb radii
+const double _kPreferredTotalWidth = _kPreferredTrackWidth + 2 * _kReactionRadius;
+const double _kMinimumTotalWidth = _kMinimumTrackWidth + 2 * _kReactionRadius;
+
 final Color _kInactiveTrackColor = Colors.grey.shade400;
 final Color _kActiveTrackColor = Colors.grey;
 final Tween<double> _kReactionRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kReactionRadius);
@@ -258,14 +266,11 @@
   return label == null ? 0.0 : _kLabelBalloonRadius * 2.0;
 }
 
-BoxConstraints _getAdditionalConstraints(String label) {
-  return new BoxConstraints.tightFor(
-    width: _kTrackWidth + 2 * _kReactionRadius,
-    height: 2 * _kReactionRadius + _getAdditionalHeightForLabel(label)
-  );
+double _getPreferredTotalHeight(String label) {
+  return 2 * _kReactionRadius + _getAdditionalHeightForLabel(label);
 }
 
-class _RenderSlider extends RenderConstrainedBox implements SemanticsActionHandler {
+class _RenderSlider extends RenderBox implements SemanticsActionHandler {
   _RenderSlider({
     @required double value,
     int divisions,
@@ -279,8 +284,7 @@
        _divisions = divisions,
        _activeColor = activeColor,
        _thumbOpenAtMin = thumbOpenAtMin,
-       _textTheme = textTheme,
-        super(additionalConstraints: _getAdditionalConstraints(label)) {
+       _textTheme = textTheme {
     assert(value != null && value >= 0.0 && value <= 1.0);
     this.label = label;
     final GestureArenaTeam team = new GestureArenaTeam();
@@ -335,7 +339,6 @@
     if (value == _label)
       return;
     _label = value;
-    additionalConstraints = _getAdditionalConstraints(_label);
     if (value != null) {
       // TODO(abarth): Handle textScaleFactor.
       // https://github.com/flutter/flutter/issues/5938
@@ -348,7 +351,7 @@
     } else {
       _labelPainter.text = null;
     }
-    markNeedsPaint();
+    markNeedsLayout();
   }
 
   Color get activeColor => _activeColor;
@@ -448,11 +451,45 @@
     }
   }
 
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return _kMinimumTotalWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    // This doesn't quite match the definition of computeMaxIntrinsicWidth,
+    // but it seems within the spirit...
+    return _kPreferredTotalWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return _getPreferredTotalHeight(label);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _getPreferredTotalHeight(label);
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  void performResize() {
+    size = new Size(
+      constraints.hasBoundedWidth ? constraints.maxWidth : _kPreferredTotalWidth,
+      constraints.hasBoundedHeight ? constraints.maxHeight : _getPreferredTotalHeight(label),
+    );
+  }
+
   @override
   void paint(PaintingContext context, Offset offset) {
     final Canvas canvas = context.canvas;
 
-    final double trackLength = _trackLength;
+    final double trackLength = size.width - 2 * _kReactionRadius;
     final bool enabled = isInteractive;
     final double value = _position.value;
 
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 56c1c9e..3942e40 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -49,17 +49,20 @@
         builder: (BuildContext context, StateSetter setState) {
           return new Material(
             child: new Center(
-              child: new Slider(
-                key: sliderKey,
-                min: 0.0,
-                max: 100.0,
-                divisions: 10,
-                value: value,
-                onChanged: (double newValue) {
-                  setState(() {
-                    value = newValue;
-                  });
-                },
+              child: new SizedBox(
+                width: 144.0 + 2 * 16.0, // _kPreferredTotalWidth
+                child: new Slider(
+                  key: sliderKey,
+                  min: 0.0,
+                  max: 100.0,
+                  divisions: 10,
+                  value: value,
+                  onChanged: (double newValue) {
+                    setState(() {
+                      value = newValue;
+                    });
+                  },
+                ),
               ),
             ),
           );
@@ -188,4 +191,42 @@
 
     await gesture.up();
   });
+
+  testWidgets('Slider sizing', (WidgetTester tester) async {
+    await tester.pumpWidget(const Material(
+      child: const Center(
+        child: const Slider(
+          value: 0.5,
+          onChanged: null,
+        ),
+      ),
+    ));
+    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(800.0, 600.0));
+
+    await tester.pumpWidget(const Material(
+      child: const Center(
+        child: const IntrinsicWidth(
+          child: const Slider(
+            value: 0.5,
+            onChanged: null,
+          ),
+        ),
+      ),
+    ));
+    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 600.0));
+
+    await tester.pumpWidget(const Material(
+      child: const Center(
+        child: const OverflowBox(
+          maxWidth: double.INFINITY,
+          maxHeight: double.INFINITY,
+          child: const Slider(
+            value: 0.5,
+            onChanged: null,
+          ),
+        ),
+      ),
+    ));
+    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 32.0));
+  });
 }