Slider Visual Update (#14901)

This implements an update to the look of the Slider widget.

Specifically, it does the following:

* Adds the ability to customize the colors of all components of the slider
* Adds the ability to customize the shape of the slider thumb and value indicator
* Adds the ability to show the value indicator on continuous sliders
* Updates the default value indicator to be a "paddle" shape with new animations.
* Changes the tick marks to be visible all the time on discrete sliders
* Fixes a memory leak of an animation controller.
* Removes "thumbOpenAtMin" flag, which is no longer needed, and can be emulated by the
custom thumb shape support. It was not widely used.
* Adds tests for all of the new features.
diff --git a/examples/flutter_gallery/lib/demo/material/slider_demo.dart b/examples/flutter_gallery/lib/demo/material/slider_demo.dart
index fbd2212..0746601 100644
--- a/examples/flutter_gallery/lib/demo/material/slider_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/slider_demo.dart
@@ -31,7 +31,6 @@
                   value: _value,
                   min: 0.0,
                   max: 100.0,
-                  thumbOpenAtMin: true,
                   onChanged: (double value) {
                     setState(() {
                       _value = value;
@@ -44,7 +43,7 @@
             new Column(
               mainAxisSize: MainAxisSize.min,
               children: const <Widget> [
-                const Slider(value: 0.25, thumbOpenAtMin: true, onChanged: null),
+                const Slider(value: 0.25, onChanged: null),
                 const Text('Disabled'),
               ]
             ),
@@ -57,7 +56,6 @@
                   max: 100.0,
                   divisions: 5,
                   label: '${_discreteValue.round()}',
-                  thumbOpenAtMin: true,
                   onChanged: (double value) {
                     setState(() {
                       _discreteValue = value;
diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart
index 675262c..7e857c4 100644
--- a/packages/flutter/lib/material.dart
+++ b/packages/flutter/lib/material.dart
@@ -77,6 +77,7 @@
 export 'src/material/scrollbar.dart';
 export 'src/material/shadows.dart';
 export 'src/material/slider.dart';
+export 'src/material/slider_theme.dart';
 export 'src/material/snack_bar.dart';
 export 'src/material/stepper.dart';
 export 'src/material/switch.dart';
diff --git a/packages/flutter/lib/src/material/button_theme.dart b/packages/flutter/lib/src/material/button_theme.dart
index 50570f3..9529ceb 100644
--- a/packages/flutter/lib/src/material/button_theme.dart
+++ b/packages/flutter/lib/src/material/button_theme.dart
@@ -135,7 +135,7 @@
 /// A button theme can be specified as part of the overall Material theme
 /// using [ThemeData.buttomTheme]. The Material theme's button theme data
 /// can be overridden with [ButtonTheme].
-class ButtonThemeData {
+class ButtonThemeData extends Diagnosticable {
   /// Create a button theme object that can be used with [ButtonTheme]
   /// or [ThemeData].
   ///
@@ -251,4 +251,18 @@
       shape,
     );
   }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    final ButtonThemeData defaultTheme = const ButtonThemeData();
+    description.add(new EnumProperty<ButtonTextTheme>('textTheme', textTheme,
+        defaultValue: defaultTheme.textTheme));
+    description.add(new DoubleProperty('minWidth', minWidth, defaultValue: defaultTheme.minWidth));
+    description.add(new DoubleProperty('height', height, defaultValue: defaultTheme.height));
+    description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding,
+        defaultValue: defaultTheme.padding));
+    description.add(
+        new DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultTheme.shape));
+  }
 }
diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart
index 263d658..cff8cac 100644
--- a/packages/flutter/lib/src/material/ink_well.dart
+++ b/packages/flutter/lib/src/material/ink_well.dart
@@ -312,7 +312,7 @@
   ///  * [highlightColor], the color of the highlight.
   ///  * [InkSplash.splashFactory], which defines the default splash.
   ///  * [InkRipple.splashFactory], which defines a splash that spreads out
-  ///    more aggresively than the default.
+  ///    more aggressively than the default.
   final InteractiveInkFeatureFactory splashFactory;
 
   /// Whether detected gestures should provide acoustic and/or haptic feedback.
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart
index c51480f..dfc4969 100644
--- a/packages/flutter/lib/src/material/input_decorator.dart
+++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -53,7 +53,7 @@
 
 // Used to interpolate between two InputBorders.
 class _InputBorderTween extends Tween<InputBorder> {
-  _InputBorderTween({ InputBorder begin, InputBorder end }) : super(begin: begin, end: end);
+  _InputBorderTween({InputBorder begin, InputBorder end}) : super(begin: begin, end: end);
 
   @override
   InputBorder lerp(double t) => ShapeBorder.lerp(begin, end, t);
@@ -108,7 +108,7 @@
     @required this.border,
     @required this.gap,
     @required this.gapAnimation,
-    this.child
+    this.child,
   }) : assert(border != null),
        assert(gap != null),
        super(key: key);
@@ -2164,7 +2164,7 @@
 /// The [InputDecoration.applyDefaults] method is used to combine a input
 /// decoration theme with an [InputDecoration] object.
 @immutable
-class InputDecorationTheme {
+class InputDecorationTheme extends Diagnosticable {
   /// Creates a value for [ThemeData.inputDecorationTheme] that
   /// defines default values for [InputDecorator].
   ///
@@ -2300,4 +2300,36 @@
   ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
   ///    rounded rectangle around the input decorator's container.
   final InputBorder border;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    final InputDecorationTheme defaultTheme = const InputDecorationTheme();
+    description.add(new DiagnosticsProperty<TextStyle>('labelStyle', labelStyle,
+        defaultValue: defaultTheme.labelStyle));
+    description.add(new DiagnosticsProperty<TextStyle>('helperStyle', helperStyle,
+        defaultValue: defaultTheme.helperStyle));
+    description.add(new DiagnosticsProperty<TextStyle>('hintStyle', hintStyle,
+        defaultValue: defaultTheme.hintStyle));
+    description.add(new DiagnosticsProperty<TextStyle>('errorStyle', errorStyle,
+        defaultValue: defaultTheme.errorStyle));
+    description
+        .add(new DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense));
+    description.add(new DiagnosticsProperty<EdgeInsets>('contentPadding', contentPadding,
+        defaultValue: defaultTheme.contentPadding));
+    description.add(new DiagnosticsProperty<bool>('isCollapsed', isCollapsed,
+        defaultValue: defaultTheme.isCollapsed));
+    description.add(new DiagnosticsProperty<TextStyle>('prefixStyle', prefixStyle,
+        defaultValue: defaultTheme.prefixStyle));
+    description.add(new DiagnosticsProperty<TextStyle>('suffixStyle', suffixStyle,
+        defaultValue: defaultTheme.suffixStyle));
+    description.add(new DiagnosticsProperty<TextStyle>('counterStyle', counterStyle,
+        defaultValue: defaultTheme.counterStyle));
+    description
+        .add(new DiagnosticsProperty<bool>('filled', filled, defaultValue: defaultTheme.filled));
+    description.add(new DiagnosticsProperty<Color>('fillColor', fillColor,
+        defaultValue: defaultTheme.fillColor));
+    description.add(
+        new DiagnosticsProperty<InputBorder>('border', border, defaultValue: defaultTheme.border));
+  }
 }
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index 8b11c61..f1b2898 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -9,39 +9,61 @@
 import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart';
 
-import 'colors.dart';
 import 'constants.dart';
 import 'debug.dart';
+import 'material.dart';
+import 'slider_theme.dart';
 import 'theme.dart';
-import 'typography.dart';
 
-/// A material design slider.
+/// A Material Design slider.
 ///
 /// Used to select from a range of values.
 ///
 /// A slider can be used to select from either a continuous or a discrete set of
-/// values. The default is use a continuous range of values from [min] to [max].
-/// To use discrete values, use a non-null value for [divisions], which
+/// values. The default is to use a continuous range of values from [min] to
+/// [max]. To use discrete values, use a non-null value for [divisions], which
 /// indicates the number of discrete intervals. For example, if [min] is 0.0 and
-/// [max] is 50.0 and [divisions] is 5, then the slider can take on the values
+/// [max] is 50.0 and [divisions] is 5, then the slider can take on the
 /// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
 ///
+/// The terms for the parts of a slider are:
+///
+///  * The "thumb", which is a shape that slides horizontally when the user
+///    drags it.
+///  * The "rail", which is the line that the slider thumb slides along.
+///  * The "value indicator", which is a shape that pops up when the user
+///    is dragging the thumb to indicate the value being selected.
+///  * The "active" side of the slider is the side between the thumb and the
+///    minimum value.
+///  * The "inactive" side of the slider is the side between the thumb and the
+///    maximum value.
+///
 /// 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 widget 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.
 ///
 /// 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
+/// given unbounded constraints, it will attempt to make the rail 144 pixels
 /// wide (with margins on each side) and will shrink-wrap vertically.
 ///
 /// Requires one of its ancestors to be a [Material] widget.
 ///
+/// To determine how it should be displayed (e.g. colors, thumb shape, etc.),
+/// a slider uses the [SliderThemeData] available from either a [SliderTheme]
+/// widget or the [ThemeData.sliderTheme] a [Theme] widget above it in the
+/// widget tree. You can also override some of the colors with the [activeColor]
+/// and [inactiveColor] properties, although more fine-grained control of the
+/// look is achieved using a [SliderThemeData].
+///
 /// See also:
 ///
+///  * [SliderTheme] and [SliderThemeData] for information about controlling
+///    the visual appearance of the slider.
 ///  * [Radio], for selecting among a set of explicit values.
 ///  * [Checkbox] and [Switch], for toggling a particular value on or off.
 ///  * <https://material.google.com/components/sliders.html>
@@ -55,6 +77,10 @@
   ///
   /// * [value] determines currently selected value for this slider.
   /// * [onChanged] is called when the user selects a new value for the slider.
+  ///
+  /// You can override some of the colors with the [activeColor] and
+  /// [inactiveColor] properties, although more fine-grained control of the
+  /// appearance is achieved using a [SliderThemeData].
   const Slider({
     Key key,
     @required this.value,
@@ -65,14 +91,12 @@
     this.label,
     this.activeColor,
     this.inactiveColor,
-    this.thumbOpenAtMin: false,
   }) : assert(value != null),
        assert(min != null),
        assert(max != null),
        assert(min <= max),
        assert(value >= min && value <= max),
        assert(divisions == null || divisions > 0),
-       assert(thumbOpenAtMin != null),
        super(key: key);
 
   /// The currently selected value for this slider.
@@ -131,32 +155,43 @@
 
   /// A label to show above the slider when the slider is active.
   ///
-  /// Typically used to display the value of a discrete slider.
+  /// It is used to display the value of a discrete slider, and it is displayed
+  /// as part of the value indicator shape.
+  ///
+  /// The label is rendered using the active [ThemeData]'s
+  /// [ThemeData.accentTextTheme.body2] text style.
+  ///
+  /// If null, then the value indicator will not be displayed.
+  ///
+  /// See also:
+  ///
+  ///  * [SliderComponentShape] for how to create a custom value indicator
+  ///    shape.
   final String label;
 
-  /// The color to use for the portion of the slider that has been selected.
+  /// The color to use for the portion of the slider rail that is active.
   ///
-  /// Defaults to accent color of the current [Theme].
+  /// The "active" side of the slider is the side between the thumb and the
+  /// minimum value.
+  ///
+  /// Defaults to [SliderTheme.activeRailColor] of the current [SliderTheme].
+  ///
+  /// Using a [SliderTheme] gives much more fine-grained control over the
+  /// appearance of various components of the slider.
   final Color activeColor;
 
-  /// The color for the unselected portion of the slider.
+  /// The color for the inactive portion of the slider rail.
   ///
-  /// Defaults to the unselected widget color of the current [Theme].
+  /// The "inactive" side of the slider is the side between the thumb and the
+  /// maximum value.
+  ///
+  /// Defaults to the [SliderTheme.inactiveRailColor] of the current
+  /// [SliderTheme].
+  ///
+  /// Using a [SliderTheme] gives much more fine-grained control over the
+  /// appearance of various components of the slider.
   final Color inactiveColor;
 
-  /// Whether the thumb should be an open circle when the slider is at its minimum position.
-  ///
-  /// When this property is false, the thumb does not change when it the slider
-  /// reaches its minimum position.
-  ///
-  /// This property is useful, for example, when the minimum value represents a
-  /// qualitatively different state. For a slider that controls the volume of
-  /// a sound, for example, the minimum value represents "no sound at all,"
-  /// which is qualitatively different from even a very soft sound.
-  ///
-  /// Defaults to false.
-  final bool thumbOpenAtMin;
-
   @override
   _SliderState createState() => new _SliderState();
 
@@ -170,44 +205,88 @@
 }
 
 class _SliderState extends State<Slider> with TickerProviderStateMixin {
-  _SliderState() {
-    _reactionController = new AnimationController(
+  static const Duration enableAnimationDuration = const Duration(milliseconds: 75);
+  static const Duration positionAnimationDuration = const Duration(milliseconds: 75);
+
+  AnimationController reactionController;
+  AnimationController enableController;
+  AnimationController positionController;
+
+  @override
+  void initState() {
+    super.initState();
+    reactionController = new AnimationController(
       duration: kRadialReactionDuration,
       vsync: this,
     );
+    enableController = new AnimationController(
+      duration: enableAnimationDuration,
+      vsync: this,
+    );
+    positionController = new AnimationController(
+      duration: positionAnimationDuration,
+      vsync: this,
+    );
   }
 
-  void _handleChanged(double value) {
-    assert(widget.onChanged != null);
-    widget.onChanged(value * (widget.max - widget.min) + widget.min);
-  }
-
   @override
   void dispose() {
-    _reactionController?.dispose();
+    reactionController.dispose();
+    enableController.dispose();
+    positionController.dispose();
     super.dispose();
   }
 
-  // Have to keep the reaction controller here so that we may dispose of it
-  // properly.
-  AnimationController _reactionController;
+  void _handleChanged(double value) {
+    assert(widget.onChanged != null);
+    final double transformedValue = _lerp(value);
+    widget.onChanged(transformedValue);
+  }
+
+  // Returns a number between min and max, proportional to value, which must
+  // be between 0.0 and 1.0.
+  double _lerp(double value) {
+    assert(value >= 0.0);
+    assert(value <= 1.0);
+    return value * (widget.max - widget.min) + widget.min;
+  }
+
+  // Returns a number between 0.0 and 1.0, given a value between min and max.
+  double _unlerp(double value) {
+    assert(value <= widget.max);
+    assert(value >= widget.min);
+    return widget.max > widget.min ? (widget.value - widget.min) / (widget.max - widget.min) : 0.0;
+  }
 
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterial(context));
-    final ThemeData theme = Theme.of(context);
+
+    SliderThemeData sliderTheme = SliderTheme.of(context);
+
+    // If the widget has active or inactive colors specified, then we plug them
+    // in to the slider theme as best we can. If the developer wants more
+    // control than that, then they need to use a SliderTheme.
+    if (widget.activeColor != null || widget.inactiveColor != null) {
+      sliderTheme = sliderTheme.copyWith(
+        activeRailColor: widget.activeColor,
+        inactiveRailColor: widget.inactiveColor,
+        activeTickMarkColor: widget.inactiveColor,
+        inactiveTickMarkColor: widget.activeColor,
+        thumbColor: widget.activeColor,
+        valueIndicatorColor: widget.activeColor,
+        overlayColor: widget.activeColor?.withAlpha(0x29),
+      );
+    }
+
     return new _SliderRenderObjectWidget(
-      value: widget.max > widget.min ? (widget.value - widget.min) / (widget.max - widget.min) : 0.0,
+      value: _unlerp(widget.value),
       divisions: widget.divisions,
       label: widget.label,
-      activeColor: widget.activeColor ?? theme.accentColor,
-      inactiveColor: widget.inactiveColor ?? theme.unselectedWidgetColor,
-      thumbOpenAtMin: widget.thumbOpenAtMin,
-      textTheme: theme.accentTextTheme,
+      sliderTheme: sliderTheme,
       textScaleFactor: MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0,
       onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
-      vsync: this,
-      reactionController: _reactionController,
+      state: this,
     );
   }
 }
@@ -218,27 +297,19 @@
     this.value,
     this.divisions,
     this.label,
-    this.activeColor,
-    this.inactiveColor,
-    this.thumbOpenAtMin,
-    this.textTheme,
+    this.sliderTheme,
     this.textScaleFactor,
     this.onChanged,
-    this.vsync,
-    this.reactionController,
+    this.state,
   }) : super(key: key);
 
   final double value;
   final int divisions;
   final String label;
-  final Color activeColor;
-  final Color inactiveColor;
-  final bool thumbOpenAtMin;
-  final TextTheme textTheme;
+  final SliderThemeData sliderTheme;
   final double textScaleFactor;
   final ValueChanged<double> onChanged;
-  final TickerProvider vsync;
-  final AnimationController reactionController;
+  final _SliderState state;
 
   @override
   _RenderSlider createRenderObject(BuildContext context) {
@@ -246,14 +317,11 @@
       value: value,
       divisions: divisions,
       label: label,
-      activeColor: activeColor,
-      inactiveColor: inactiveColor,
-      thumbOpenAtMin: thumbOpenAtMin,
-      textTheme: textTheme,
+      sliderTheme: sliderTheme,
+      theme: Theme.of(context),
       textScaleFactor: textScaleFactor,
       onChanged: onChanged,
-      vsync: vsync,
-      reactionController: reactionController,
+      state: state,
       textDirection: Directionality.of(context),
     );
   }
@@ -264,74 +332,47 @@
       ..value = value
       ..divisions = divisions
       ..label = label
-      ..activeColor = activeColor
-      ..inactiveColor = inactiveColor
-      ..thumbOpenAtMin = thumbOpenAtMin
-      ..textTheme = textTheme
+      ..sliderTheme = sliderTheme
+      ..theme = Theme.of(context)
       ..textScaleFactor = textScaleFactor
       ..onChanged = onChanged
       ..textDirection = Directionality.of(context);
-      // Ticker provider cannot change since there's a 1:1 relationship between
-      // the _SliderRenderObjectWidget object and the _SliderState object.
+    // Ticker provider cannot change since there's a 1:1 relationship between
+    // the _SliderRenderObjectWidget object and the _SliderState object.
   }
 }
 
-const double _kThumbRadius = 6.0;
-const double _kActiveThumbRadius = 9.0;
-const double _kDisabledThumbRadius = 4.0;
-const double _kReactionRadius = 16.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;
+const double _overlayRadius = 16.0;
+const double _overlayDiameter = _overlayRadius * 2.0;
+const double _railHeight = 2.0;
+const double _preferredRailWidth = 144.0;
+const double _preferredTotalWidth = _preferredRailWidth + _overlayDiameter;
 
-final Color _kActiveTrackColor = Colors.grey;
-final Tween<double> _kReactionRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kReactionRadius);
-final Tween<double> _kThumbRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kActiveThumbRadius);
-final ColorTween _kTickColorTween = new ColorTween(begin: Colors.transparent, end: Colors.black54);
-const Duration _kDiscreteTransitionDuration = const Duration(milliseconds: 500);
-
-const double _kLabelBalloonRadius = 14.0;
-final Tween<double> _kLabelBalloonCenterTween = new Tween<double>(begin: 0.0, end: -_kLabelBalloonRadius * 2.0);
-final Tween<double> _kLabelBalloonRadiusTween = new Tween<double>(begin: _kThumbRadius, end: _kLabelBalloonRadius);
-final Tween<double> _kLabelBalloonTipTween = new Tween<double>(begin: 0.0, end: -8.0);
-final double _kLabelBalloonTipAttachmentRatio = math.sin(math.PI / 4.0);
-
-const double _kAdjustmentUnit = 0.1; // Matches iOS implementation of material slider.
-
-double _getAdditionalHeightForLabel(String label) {
-  return label == null ? 0.0 : _kLabelBalloonRadius * 2.0;
-}
-
-double _getPreferredTotalHeight(String label) {
-  return 2 * _kReactionRadius + _getAdditionalHeightForLabel(label);
-}
+const double _adjustmentUnit = 0.1; // Matches iOS implementation of material slider.
+final Tween<double> _overlayRadiusTween = new Tween<double>(begin: 0.0, end: _overlayRadius);
 
 class _RenderSlider extends RenderBox {
   _RenderSlider({
     @required double value,
     int divisions,
     String label,
-    Color activeColor,
-    Color inactiveColor,
-    bool thumbOpenAtMin,
-    TextTheme textTheme,
+    SliderThemeData sliderTheme,
+    ThemeData theme,
     double textScaleFactor,
     ValueChanged<double> onChanged,
-    TickerProvider vsync,
+    @required _SliderState state,
     @required TextDirection textDirection,
-    @required AnimationController reactionController,
   }) : assert(value != null && value >= 0.0 && value <= 1.0),
+       assert(state != null),
        assert(textDirection != null),
        _label = label,
        _value = value,
        _divisions = divisions,
-       _activeColor = activeColor,
-       _inactiveColor = inactiveColor,
-       _thumbOpenAtMin = thumbOpenAtMin,
-       _textTheme = textTheme,
+       _sliderTheme = sliderTheme,
+       _theme = theme,
        _textScaleFactor = textScaleFactor,
        _onChanged = onChanged,
+       _state = state,
        _textDirection = textDirection {
     _updateLabelPainter();
     final GestureArenaTeam team = new GestureArenaTeam();
@@ -342,104 +383,106 @@
       ..onEnd = _handleDragEnd;
     _tap = new TapGestureRecognizer()
       ..team = team
+      ..onTapCancel = _endInteraction
+      ..onTapDown = _handleTapDown
       ..onTapUp = _handleTapUp;
-    _reactionController = reactionController;
-    _reaction = new CurvedAnimation(
-      parent: _reactionController,
-      curve: Curves.fastOutSlowIn
-    )..addListener(markNeedsPaint);
-    _position = new AnimationController(
-      value: value,
-      duration: _kDiscreteTransitionDuration,
-      vsync: vsync,
-    )..addListener(markNeedsPaint);
+    _reaction = new CurvedAnimation(parent: state.reactionController, curve: Curves.fastOutSlowIn)
+      ..addListener(markNeedsPaint);
+    state.enableController.value = isInteractive ? 1.0 : 0.0;
+    _enableAnimation = new CurvedAnimation(parent: state.enableController, curve: Curves.easeInOut)
+      ..addListener(markNeedsPaint);
+    state.positionController.value = _value;
   }
 
   double get value => _value;
   double _value;
+
+  _SliderState _state;
+
   set value(double newValue) {
     assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
-    if (newValue == _value)
+    final double convertedValue = isDiscrete ? _discretize(newValue) : newValue;
+    if (convertedValue == _value) {
       return;
-    _value = newValue;
-    if (divisions != null)
-      _position.animateTo(newValue, curve: Curves.fastOutSlowIn);
-    else
-      _position.value = newValue;
+    }
+    _value = convertedValue;
+    if (isDiscrete) {
+      _state.positionController.animateTo(convertedValue, curve: Curves.easeInOut);
+    } else {
+      _state.positionController.value = convertedValue;
+    }
   }
 
   int get divisions => _divisions;
   int _divisions;
+
   set divisions(int value) {
-    if (value == _divisions)
+    if (value == _divisions) {
       return;
+    }
     _divisions = value;
     markNeedsPaint();
   }
 
   String get label => _label;
   String _label;
+
   set label(String value) {
-    if (value == _label)
+    if (value == _label) {
       return;
+    }
     _label = value;
     _updateLabelPainter();
   }
 
-  Color get activeColor => _activeColor;
-  Color _activeColor;
-  set activeColor(Color value) {
-    if (value == _activeColor)
+  SliderThemeData get sliderTheme => _sliderTheme;
+  SliderThemeData _sliderTheme;
+
+  set sliderTheme(SliderThemeData value) {
+    if (value == _sliderTheme) {
       return;
-    _activeColor = value;
+    }
+    _sliderTheme = value;
     markNeedsPaint();
   }
 
-  Color get inactiveColor => _inactiveColor;
-  Color _inactiveColor;
-  set inactiveColor(Color value) {
-    if (value == _inactiveColor)
-      return;
-    _inactiveColor = value;
-    markNeedsPaint();
-  }
+  ThemeData get theme => _theme;
+  ThemeData _theme;
 
-  bool get thumbOpenAtMin => _thumbOpenAtMin;
-  bool _thumbOpenAtMin;
-  set thumbOpenAtMin(bool value) {
-    if (value == _thumbOpenAtMin)
+  set theme(ThemeData value) {
+    if (value == _theme) {
       return;
-    _thumbOpenAtMin = value;
-    markNeedsPaint();
-  }
-
-  TextTheme get textTheme => _textTheme;
-  TextTheme _textTheme;
-  set textTheme(TextTheme value) {
-    if (value == _textTheme)
-      return;
-    _textTheme = value;
+    }
+    _theme = value;
     markNeedsPaint();
   }
 
   double get textScaleFactor => _textScaleFactor;
   double _textScaleFactor;
+
   set textScaleFactor(double value) {
-    if (value == _textScaleFactor)
+    if (value == _textScaleFactor) {
       return;
+    }
     _textScaleFactor = value;
     _updateLabelPainter();
-    markNeedsPaint();
   }
 
   ValueChanged<double> get onChanged => _onChanged;
   ValueChanged<double> _onChanged;
+
   set onChanged(ValueChanged<double> value) {
-    if (value == _onChanged)
+    if (value == _onChanged) {
       return;
+    }
     final bool wasInteractive = isInteractive;
     _onChanged = value;
     if (wasInteractive != isInteractive) {
+      if (isInteractive) {
+        _state.enableController.forward();
+      } else {
+        _state.enableController.reverse();
+      }
       markNeedsPaint();
       markNeedsSemanticsUpdate();
     }
@@ -447,21 +490,23 @@
 
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
+
   set textDirection(TextDirection value) {
     assert(value != null);
-    if (value == _textDirection)
+    if (value == _textDirection) {
       return;
+    }
     _textDirection = value;
     _updateLabelPainter();
   }
 
   void _updateLabelPainter() {
     if (label != null) {
+      // We have to account for the text scale factor in the supplied theme.
+      final TextStyle style = _theme.accentTextTheme.body2
+          .copyWith(fontSize: _theme.accentTextTheme.body2.fontSize * _textScaleFactor);
       _labelPainter
-        ..text = new TextSpan(
-          style: _textTheme.body1.copyWith(fontSize: 10.0 * _textScaleFactor),
-          text: label,
-        )
+        ..text = new TextSpan(style: style, text: label)
         ..textDirection = textDirection
         ..layout();
     } else {
@@ -473,14 +518,11 @@
     markNeedsLayout();
   }
 
-  double get _trackLength => size.width - 2.0 * _kReactionRadius;
+  double get _railLength => size.width - _overlayDiameter;
 
   Animation<double> _reaction;
-  AnimationController _reactionController;
-
-  AnimationController _position;
+  Animation<double> _enableAnimation;
   final TextPainter _labelPainter = new TextPainter();
-
   HorizontalDragGestureRecognizer _drag;
   TapGestureRecognizer _tap;
   bool _active = false;
@@ -488,6 +530,8 @@
 
   bool get isInteractive => onChanged != null;
 
+  bool get isDiscrete => divisions != null && divisions > 0;
+
   double _getValueFromVisualPosition(double visualPosition) {
     switch (textDirection) {
       case TextDirection.rtl:
@@ -499,29 +543,40 @@
   }
 
   double _getValueFromGlobalPosition(Offset globalPosition) {
-    final double visualPosition = (globalToLocal(globalPosition).dx - _kReactionRadius) / _trackLength;
+    final double visualPosition = (globalToLocal(globalPosition).dx - _overlayRadius) / _railLength;
     return _getValueFromVisualPosition(visualPosition);
   }
 
   double _discretize(double value) {
     double result = value.clamp(0.0, 1.0);
-    if (divisions != null)
+    if (isDiscrete) {
       result = (result * divisions).round() / divisions;
+    }
     return result;
   }
 
-  void _handleDragStart(DragStartDetails details) {
+  void _startInteraction(Offset globalPosition) {
     if (isInteractive) {
       _active = true;
-      _currentDragValue = _getValueFromGlobalPosition(details.globalPosition);
+      _currentDragValue = _getValueFromGlobalPosition(globalPosition);
       onChanged(_discretize(_currentDragValue));
-      _reactionController.forward();
+      _state.reactionController.forward();
     }
   }
 
+  void _endInteraction() {
+    if (_active) {
+      _active = false;
+      _currentDragValue = 0.0;
+      _state.reactionController.reverse();
+    }
+  }
+
+  void _handleDragStart(DragStartDetails details) => _startInteraction(details.globalPosition);
+
   void _handleDragUpdate(DragUpdateDetails details) {
     if (isInteractive) {
-      final double valueDelta = details.primaryDelta / _trackLength;
+      final double valueDelta = details.primaryDelta / _railLength;
       switch (textDirection) {
         case TextDirection.rtl:
           _currentDragValue -= valueDelta;
@@ -534,18 +589,11 @@
     }
   }
 
-  void _handleDragEnd(DragEndDetails details) {
-    if (_active) {
-      _active = false;
-      _currentDragValue = 0.0;
-      _reactionController.reverse();
-    }
-  }
+  void _handleDragEnd(DragEndDetails details) => _endInteraction();
 
-  void _handleTapUp(TapUpDetails details) {
-    if (isInteractive && !_active)
-      onChanged(_discretize(_getValueFromGlobalPosition(details.globalPosition)));
-  }
+  void _handleTapDown(TapDownDetails details) => _startInteraction(details.globalPosition);
+
+  void _handleTapUp(TapUpDetails details) => _endInteraction();
 
   @override
   bool hitTestSelf(Offset position) => true;
@@ -562,25 +610,22 @@
 
   @override
   double computeMinIntrinsicWidth(double height) {
-    return _kMinimumTotalWidth;
+    return math.max(_overlayDiameter,
+        _sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width);
   }
 
   @override
   double computeMaxIntrinsicWidth(double height) {
     // This doesn't quite match the definition of computeMaxIntrinsicWidth,
     // but it seems within the spirit...
-    return _kPreferredTotalWidth;
+    return _preferredTotalWidth;
   }
 
   @override
-  double computeMinIntrinsicHeight(double width) {
-    return _getPreferredTotalHeight(label);
-  }
+  double computeMinIntrinsicHeight(double width) => _overlayDiameter;
 
   @override
-  double computeMaxIntrinsicHeight(double width) {
-    return _getPreferredTotalHeight(label);
-  }
+  double computeMaxIntrinsicHeight(double width) => _overlayDiameter;
 
   @override
   bool get sizedByParent => true;
@@ -588,123 +633,174 @@
   @override
   void performResize() {
     size = new Size(
-      constraints.hasBoundedWidth ? constraints.maxWidth : _kPreferredTotalWidth,
-      constraints.hasBoundedHeight ? constraints.maxHeight : _getPreferredTotalHeight(label),
+      constraints.hasBoundedWidth ? constraints.maxWidth : _preferredTotalWidth,
+      constraints.hasBoundedHeight ? constraints.maxHeight : _overlayDiameter,
     );
   }
 
+  void _paintTickMarks(
+      Canvas canvas, Rect railLeft, Rect railRight, Paint leftPaint, Paint rightPaint) {
+    if (isDiscrete) {
+      // The ticks are tiny circles that are the same height as the rail.
+      const double tickRadius = _railHeight / 2.0;
+      final double railWidth = railRight.right - railLeft.left;
+      final double dx = (railWidth - _railHeight) / divisions;
+      // If the ticks would be too dense, don't bother painting them.
+      if (dx >= 3.0 * _railHeight) {
+        for (int i = 0; i <= divisions; i += 1) {
+          final double left = railLeft.left + i * dx;
+          final Offset center = new Offset(left + tickRadius, railLeft.top + tickRadius);
+          if (railLeft.contains(center)) {
+            canvas.drawCircle(center, tickRadius, leftPaint);
+          } else if (railRight.contains(center)) {
+            canvas.drawCircle(center, tickRadius, rightPaint);
+          }
+        }
+      }
+    }
+  }
+
+  void _paintOverlay(Canvas canvas, Offset center) {
+    if (!_reaction.isDismissed) {
+      // TODO(gspencer) : We don't really follow the spec here for overlays.
+      // The spec says to use 16% opacity for drawing over light material,
+      // and 32% for colored material, but we don't really have a way to
+      // know what the underlying color is, so there's no easy way to
+      // implement this. Choosing the "light" version for now.
+      final Paint reactionPaint = new Paint()..color = _sliderTheme.overlayColor;
+      final double radius = _overlayRadiusTween.evaluate(_reaction);
+      canvas.drawCircle(center, radius, reactionPaint);
+    }
+  }
+
   @override
   void paint(PaintingContext context, Offset offset) {
     final Canvas canvas = context.canvas;
 
-    final double trackLength = size.width - 2 * _kReactionRadius;
-    final bool enabled = isInteractive;
-    final double value = _position.value;
-    final bool thumbAtMin = value == 0.0;
+    final double railLength = size.width - 2 * _overlayRadius;
+    final double value = _state.positionController.value;
+    final ColorTween activeRailEnableColor = new ColorTween(
+        begin: _sliderTheme.disabledActiveRailColor, end: _sliderTheme.activeRailColor);
+    final ColorTween inactiveRailEnableColor = new ColorTween(
+        begin: _sliderTheme.disabledInactiveRailColor, end: _sliderTheme.inactiveRailColor);
+    final ColorTween activeTickMarkEnableColor = new ColorTween(
+        begin: _sliderTheme.disabledActiveTickMarkColor, end: _sliderTheme.activeTickMarkColor);
+    final ColorTween inactiveTickMarkEnableColor = new ColorTween(
+        begin: _sliderTheme.disabledInactiveTickMarkColor, end: _sliderTheme.inactiveTickMarkColor);
 
-    final Paint primaryPaint = new Paint()..color = enabled ? _activeColor : _inactiveColor;
-    final Paint trackPaint = new Paint()..color = _inactiveColor;
+    final Paint activeRailPaint = new Paint()
+      ..color = activeRailEnableColor.evaluate(_enableAnimation);
+    final Paint inactiveRailPaint = new Paint()
+      ..color = inactiveRailEnableColor.evaluate(_enableAnimation);
+    final Paint activeTickMarkPaint = new Paint()
+      ..color = activeTickMarkEnableColor.evaluate(_enableAnimation);
+    final Paint inactiveTickMarkPaint = new Paint()
+      ..color = inactiveTickMarkEnableColor.evaluate(_enableAnimation);
 
     double visualPosition;
-    Paint leftPaint;
-    Paint rightPaint;
+    Paint leftRailPaint;
+    Paint rightRailPaint;
+    Paint leftTickMarkPaint;
+    Paint rightTickMarkPaint;
     switch (textDirection) {
       case TextDirection.rtl:
         visualPosition = 1.0 - value;
-        leftPaint = trackPaint;
-        rightPaint = primaryPaint;
+        leftRailPaint = inactiveRailPaint;
+        rightRailPaint = activeRailPaint;
+        leftTickMarkPaint = inactiveTickMarkPaint;
+        rightTickMarkPaint = activeTickMarkPaint;
         break;
       case TextDirection.ltr:
         visualPosition = value;
-        leftPaint = primaryPaint;
-        rightPaint = trackPaint;
+        leftRailPaint = activeRailPaint;
+        rightRailPaint = inactiveRailPaint;
+        leftTickMarkPaint = activeTickMarkPaint;
+        rightTickMarkPaint = inactiveTickMarkPaint;
         break;
     }
 
-    final double additionalHeightForLabel = _getAdditionalHeightForLabel(label);
-    final double trackCenter = offset.dy + (size.height - additionalHeightForLabel) / 2.0 + additionalHeightForLabel;
-    final double trackLeft = offset.dx + _kReactionRadius;
-    final double trackTop = trackCenter - 1.0;
-    final double trackBottom = trackCenter + 1.0;
-    final double trackRight = trackLeft + trackLength;
-    final double trackActive = trackLeft + trackLength * visualPosition;
+    const double railRadius = _railHeight / 2.0;
+    const double thumbGap = 2.0;
 
-    final Offset thumbCenter = new Offset(trackActive, trackCenter);
-    final double thumbRadius = enabled ? _kThumbRadiusTween.evaluate(_reaction) : _kDisabledThumbRadius;
+    final double railVerticalCenter = offset.dy + (size.height) / 2.0;
+    final double railLeft = offset.dx + _overlayRadius;
+    final double railTop = railVerticalCenter - railRadius;
+    final double railBottom = railVerticalCenter + railRadius;
+    final double railRight = railLeft + railLength;
+    final double railActive = railLeft + railLength * visualPosition;
+    final double thumbRadius =
+        _sliderTheme.thumbShape.getPreferredSize(isInteractive, isDiscrete).width / 2.0;
+    final double railActiveLeft =
+        math.max(0.0, railActive - thumbRadius - thumbGap * (1.0 - _enableAnimation.value));
+    final double railActiveRight =
+        math.min(railActive + thumbRadius + thumbGap * (1.0 - _enableAnimation.value), railRight);
+    final Rect railLeftRect = new Rect.fromLTRB(railLeft, railTop, railActiveLeft, railBottom);
+    final Rect railRightRect = new Rect.fromLTRB(railActiveRight, railTop, railRight, railBottom);
 
-    if (enabled) {
-      if (visualPosition > 0.0)
-        canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive, trackBottom), leftPaint);
-      if (visualPosition < 1.0) {
-        final bool hasBalloon = _reaction.status != AnimationStatus.dismissed && label != null;
-        final double trackActiveDelta = hasBalloon ? 0.0 : thumbRadius - 1.0;
-        canvas.drawRect(new Rect.fromLTRB(trackActive + trackActiveDelta, trackTop, trackRight, trackBottom), rightPaint);
-      }
-    } else {
-      if (visualPosition > 0.0)
-        canvas.drawRect(new Rect.fromLTRB(trackLeft, trackTop, trackActive - _kDisabledThumbRadius - 2, trackBottom), trackPaint);
-      if (visualPosition < 1.0)
-        canvas.drawRect(new Rect.fromLTRB(trackActive + _kDisabledThumbRadius + 2, trackTop, trackRight, trackBottom), trackPaint);
+    final Offset thumbCenter = new Offset(railActive, railVerticalCenter);
+
+    // Paint the rail.
+    if (visualPosition > 0.0) {
+      canvas.drawRect(railLeftRect, leftRailPaint);
+    }
+    if (visualPosition < 1.0) {
+      canvas.drawRect(railRightRect, rightRailPaint);
     }
 
-    if (_reaction.status != AnimationStatus.dismissed) {
-      final int divisions = this.divisions;
-      if (divisions != null) {
-        const double tickWidth = 2.0;
-        final double dx = (trackLength - tickWidth) / divisions;
-        // If the ticks would be too dense, don't bother painting them.
-        if (dx >= 3 * tickWidth) {
-          final Paint tickPaint = new Paint()..color = _kTickColorTween.evaluate(_reaction);
-          for (int i = 0; i <= divisions; i += 1) {
-            final double left = trackLeft + i * dx;
-            canvas.drawRect(new Rect.fromLTRB(left, trackTop, left + tickWidth, trackBottom), tickPaint);
-          }
-        }
+    _paintOverlay(canvas, thumbCenter);
+
+    _paintTickMarks(
+      canvas,
+      railLeftRect,
+      railRightRect,
+      leftTickMarkPaint,
+      rightTickMarkPaint,
+    );
+
+    if (isInteractive && _reaction.status != AnimationStatus.dismissed && label != null) {
+      bool showValueIndicator;
+      switch (_sliderTheme.showValueIndicator) {
+        case ShowValueIndicator.onlyForDiscrete:
+          showValueIndicator = isDiscrete;
+          break;
+        case ShowValueIndicator.onlyForContinuous:
+          showValueIndicator = !isDiscrete;
+          break;
+        case ShowValueIndicator.always:
+          showValueIndicator = true;
+          break;
+        case ShowValueIndicator.never:
+          showValueIndicator = false;
+          break;
       }
-
-      if (label != null) {
-        final Offset center = new Offset(
-          trackActive,
-          _kLabelBalloonCenterTween.evaluate(_reaction) * textScaleFactor + trackCenter
+      if (showValueIndicator) {
+        _sliderTheme.valueIndicatorShape.paint(
+          context,
+          isDiscrete,
+          thumbCenter,
+          _reaction,
+          _enableAnimation,
+          _labelPainter,
+          _sliderTheme,
+          _textDirection,
+          _textScaleFactor,
+          value,
         );
-        final double radius = _kLabelBalloonRadiusTween.evaluate(_reaction) * textScaleFactor;
-        final Offset tip = new Offset(
-          trackActive,
-          _kLabelBalloonTipTween.evaluate(_reaction) * textScaleFactor + trackCenter
-        );
-        final double tipAttachment = _kLabelBalloonTipAttachmentRatio * radius;
-
-        canvas.drawCircle(center, radius, primaryPaint);
-        final Path path = new Path()
-          ..moveTo(tip.dx, tip.dy)
-          ..lineTo(center.dx - tipAttachment, center.dy + tipAttachment)
-          ..lineTo(center.dx + tipAttachment, center.dy + tipAttachment)
-          ..close();
-        canvas.drawPath(path, primaryPaint);
-        final Offset labelOffset = new Offset(
-          center.dx - _labelPainter.width / 2.0,
-          center.dy - _labelPainter.height / 2.0
-        );
-        _labelPainter.paint(canvas, labelOffset);
-        return;
-      } else {
-        final Color reactionBaseColor = thumbAtMin ? _kActiveTrackColor : _activeColor;
-        final Paint reactionPaint = new Paint()..color = reactionBaseColor.withAlpha(kRadialReactionAlpha);
-        canvas.drawCircle(thumbCenter, _kReactionRadiusTween.evaluate(_reaction), reactionPaint);
       }
     }
 
-    Paint thumbPaint = primaryPaint;
-    double thumbRadiusDelta = 0.0;
-    if (thumbAtMin && thumbOpenAtMin) {
-      thumbPaint = trackPaint;
-      // This is destructive to trackPaint.
-      thumbPaint
-        ..style = PaintingStyle.stroke
-        ..strokeWidth = 2.0;
-      thumbRadiusDelta = -1.0;
-    }
-    canvas.drawCircle(thumbCenter, thumbRadius + thumbRadiusDelta, thumbPaint);
+    _sliderTheme.thumbShape.paint(
+      context,
+      isDiscrete,
+      thumbCenter,
+      _reaction,
+      _enableAnimation,
+      label != null ? _labelPainter : null,
+      _sliderTheme,
+      _textDirection,
+      _textScaleFactor,
+      value,
+    );
   }
 
   @override
@@ -718,15 +814,17 @@
     }
   }
 
-  double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _kAdjustmentUnit;
+  double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _adjustmentUnit;
 
   void _increaseAction() {
-    if (isInteractive)
+    if (isInteractive) {
       onChanged((value + _semanticActionUnit).clamp(0.0, 1.0));
+    }
   }
 
   void _decreaseAction() {
-    if (isInteractive)
+    if (isInteractive) {
       onChanged((value - _semanticActionUnit).clamp(0.0, 1.0));
+    }
   }
 }
diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart
new file mode 100644
index 0000000..3efbacb
--- /dev/null
+++ b/packages/flutter/lib/src/material/slider_theme.dart
@@ -0,0 +1,729 @@
+// Copyright 2018 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 'dart:ui' show Path;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Applies a slider theme to descendant [Slider] widgets.
+///
+/// A slider theme describes the colors and shape choices of the slider
+/// components.
+///
+/// Descendant widgets obtain the current theme's [SliderThemeData] object using
+/// [SliderTheme.of]. When a widget uses [SliderTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// See also:
+///
+///  * [SliderThemeData], which describes the actual configuration of a slider
+///    theme.
+///  * [SliderComponentShape], which can be used to create custom shapes for
+///    the slider thumb and value indicator.
+class SliderTheme extends InheritedWidget {
+  /// Applies the given theme [data] to [child].
+  ///
+  /// The [data] and [child] arguments must not be null.
+  const SliderTheme({
+    Key key,
+    @required this.data,
+    @required Widget child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key, child: child);
+
+  /// Specifies the color and shape values for descendant slider widgets.
+  final SliderThemeData data;
+
+  /// Returns the data from the closest [SliderTheme] instance that encloses
+  /// the given context.
+  ///
+  /// Defaults to the ambient [ThemeData.sliderTheme] if there is no
+  /// [SliderTheme] in the given build context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// double _rocketThrust;
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return new SliderTheme(
+  ///     data: SliderTheme.of(context).copyWith(activeRail: Colors.orange),
+  ///     child: new Slider(
+  ///       onChanged: (double value) => setState(() => _rocketThrust = value),
+  ///       value: _rocketThrust;
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [SliderThemeData], which describes the actual configuration of a slider
+  ///    theme.
+  static SliderThemeData of(BuildContext context) {
+    final SliderTheme inheritedTheme = context.inheritFromWidgetOfExactType(SliderTheme);
+    return inheritedTheme != null ? inheritedTheme.data : Theme.of(context).sliderTheme;
+  }
+
+  @override
+  bool updateShouldNotify(SliderTheme old) => data != old.data;
+}
+
+/// Describes the conditions under which the value indicator on a [Slider]
+/// will be shown. Used with [SliderThemeData.showValueIndicator].
+///
+/// See also:
+///
+///  * [Slider], a Material Design slider widget.
+///  * [SliderThemeData], which describes the actual configuration of a slider
+///    theme.
+enum ShowValueIndicator {
+  /// The value indicator will only be shown for discrete sliders (sliders
+  /// where [Slider.divisions] is non-null).
+  onlyForDiscrete,
+
+  /// The value indicator will only be shown for continuous sliders (sliders
+  /// where [Slider.divisions] is null).
+  onlyForContinuous,
+
+  /// The value indicator will be shown for all types of sliders.
+  always,
+
+  /// The value indicator will never be shown.
+  never,
+}
+
+/// Holds the color, shape, and typography values for a material design slider
+/// theme.
+///
+/// Use this class to configure a [SliderTheme] widget, or to set the
+/// [ThemeData.sliderTheme] for a [Theme] widget.
+///
+/// To obtain the current ambient slider theme, use [SliderTheme.of].
+///
+/// The parts of a slider are:
+///
+///  * The "thumb", which is a shape that slides horizontally when the user
+///    drags it.
+///  * The "rail", which is the line that the slider thumb slides along.
+///  * The "value indicator", which is a shape that pops up when the user
+///    is dragging the thumb to indicate the value being selected.
+///  * The "active" side of the slider is the side between the thumb and the
+///    minimum value.
+///  * The "inactive" side of the slider is the side between the thumb and the
+///    maximum value.
+///  * The [Slider] is disabled when it is not accepting user input. See
+///    [Slider] for details on when this happens.
+///
+/// The thumb and the value indicator may have their shapes and behavior
+/// customized by creating your own [SliderComponentShape] that does what
+/// you want. See [RoundSliderThumbShape] and
+/// [PaddleSliderValueIndicatorShape] for examples.
+///
+/// See also:
+///
+///  * [SliderTheme] widget, which can override the slider theme of its
+///    children.
+///  * [Theme] widget, which performs a similar function to [SliderTheme],
+///    but for overall themes.
+///  * [ThemeData], which has a default [SliderThemeData].
+///  * [SliderComponentShape], to define custom slider component shapes.
+class SliderThemeData extends Diagnosticable {
+  /// Create a [SliderThemeData] given a set of exact values. All the values
+  /// must be specified.
+  ///
+  /// This will rarely be used directly. It is used by [lerp] to
+  /// create intermediate themes based on two themes.
+  ///
+  /// The simplest way to create a SliderThemeData is to use
+  /// [copyWith] on the one you get from [SliderTheme.of], or create an
+  /// entirely new one with [SliderThemeData.materialDefaults].
+  const SliderThemeData({
+    @required this.activeRailColor,
+    @required this.inactiveRailColor,
+    @required this.disabledActiveRailColor,
+    @required this.disabledInactiveRailColor,
+    @required this.activeTickMarkColor,
+    @required this.inactiveTickMarkColor,
+    @required this.disabledActiveTickMarkColor,
+    @required this.disabledInactiveTickMarkColor,
+    @required this.thumbColor,
+    @required this.disabledThumbColor,
+    @required this.overlayColor,
+    @required this.valueIndicatorColor,
+    @required this.thumbShape,
+    @required this.valueIndicatorShape,
+    @required this.showValueIndicator,
+  }) : assert(activeRailColor != null),
+       assert(inactiveRailColor != null),
+       assert(disabledActiveRailColor != null),
+       assert(disabledInactiveRailColor != null),
+       assert(activeTickMarkColor != null),
+       assert(inactiveTickMarkColor != null),
+       assert(disabledActiveTickMarkColor != null),
+       assert(disabledInactiveTickMarkColor != null),
+       assert(thumbColor != null),
+       assert(disabledThumbColor != null),
+       assert(overlayColor != null),
+       assert(valueIndicatorColor != null),
+       assert(thumbShape != null),
+       assert(valueIndicatorShape != null),
+       assert(showValueIndicator != null);
+
+  /// Generates a SliderThemeData from three main colors.
+  ///
+  /// Usually these are the primary, dark and light colors from
+  /// a [ThemeData].
+  ///
+  /// The opacities of these colors will be overridden with the Material Design
+  /// defaults when assigning them to the slider theme component colors.
+  ///
+  /// This is used to generate the default slider theme for a [ThemeData].
+  factory SliderThemeData.materialDefaults({
+    @required Color primaryColor,
+    @required Color primaryColorDark,
+    @required Color primaryColorLight,
+  }) {
+    assert(primaryColor != null);
+    assert(primaryColorDark != null);
+    assert(primaryColorLight != null);
+
+    // These are Material Design defaults, and are used to derive
+    // component Colors (with opacity) from base colors.
+    const int activeRailAlpha = 0xff;
+    const int inactiveRailAlpha = 0x3d; // 24% opacity
+    const int disabledActiveRailAlpha = 0x52; // 32% opacity
+    const int disabledInactiveRailAlpha = 0x1f; // 12% opacity
+    const int activeTickMarkAlpha = 0x8a; // 54% opacity
+    const int inactiveTickMarkAlpha = 0x8a; // 54% opacity
+    const int disabledActiveTickMarkAlpha = 0x1f; // 12% opacity
+    const int disabledInactiveTickMarkAlpha = 0x1f; // 12% opacity
+    const int thumbAlpha = 0xff;
+    const int disabledThumbAlpha = 0x52; // 32% opacity
+    const int valueIndicatorAlpha = 0xff;
+
+    // TODO(gspencer): We don't really follow the spec here for overlays.
+    // The spec says to use 16% opacity for drawing over light material,
+    // and 32% for colored material, but we don't really have a way to
+    // know what the underlying color is, so there's no easy way to
+    // implement this. Choosing the "light" version for now.
+    const int overlayLightAlpha = 0x29; // 16% opacity
+
+    return new SliderThemeData(
+      activeRailColor: primaryColor.withAlpha(activeRailAlpha),
+      inactiveRailColor: primaryColor.withAlpha(inactiveRailAlpha),
+      disabledActiveRailColor: primaryColorDark.withAlpha(disabledActiveRailAlpha),
+      disabledInactiveRailColor: primaryColorDark.withAlpha(disabledInactiveRailAlpha),
+      activeTickMarkColor: primaryColorLight.withAlpha(activeTickMarkAlpha),
+      inactiveTickMarkColor: primaryColor.withAlpha(inactiveTickMarkAlpha),
+      disabledActiveTickMarkColor: primaryColorLight.withAlpha(disabledActiveTickMarkAlpha),
+      disabledInactiveTickMarkColor: primaryColorDark.withAlpha(disabledInactiveTickMarkAlpha),
+      thumbColor: primaryColor.withAlpha(thumbAlpha),
+      disabledThumbColor: primaryColorDark.withAlpha(disabledThumbAlpha),
+      overlayColor: primaryColor.withAlpha(overlayLightAlpha),
+      valueIndicatorColor: primaryColor.withAlpha(valueIndicatorAlpha),
+      thumbShape: const RoundSliderThumbShape(),
+      valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
+      showValueIndicator: ShowValueIndicator.onlyForDiscrete,
+    );
+  }
+
+  final Color activeRailColor;
+  final Color inactiveRailColor;
+  final Color disabledActiveRailColor;
+  final Color disabledInactiveRailColor;
+  final Color activeTickMarkColor;
+  final Color inactiveTickMarkColor;
+  final Color disabledActiveTickMarkColor;
+  final Color disabledInactiveTickMarkColor;
+  final Color thumbColor;
+  final Color disabledThumbColor;
+  final Color overlayColor;
+  final Color valueIndicatorColor;
+  final SliderComponentShape thumbShape;
+  final SliderComponentShape valueIndicatorShape;
+
+  /// Whether the value indicator should be shown for different types of sliders.
+  ///
+  /// By default, [showValueIndicator] is set to
+  /// [ShowValueIndicator.onlyForDiscrete]. The value indicator is only shown
+  /// when the thumb is being touched.
+  final ShowValueIndicator showValueIndicator;
+
+  SliderThemeData copyWith({
+    Color activeRailColor,
+    Color inactiveRailColor,
+    Color disabledActiveRailColor,
+    Color disabledInactiveRailColor,
+    Color activeTickMarkColor,
+    Color inactiveTickMarkColor,
+    Color disabledActiveTickMarkColor,
+    Color disabledInactiveTickMarkColor,
+    Color thumbColor,
+    Color disabledThumbColor,
+    Color overlayColor,
+    Color valueIndicatorColor,
+    SliderComponentShape thumbShape,
+    SliderComponentShape valueIndicatorShape,
+    ShowValueIndicator showValueIndicator,
+  }) {
+    return new SliderThemeData(
+      activeRailColor: activeRailColor ?? this.activeRailColor,
+      inactiveRailColor: inactiveRailColor ?? this.inactiveRailColor,
+      disabledActiveRailColor: disabledActiveRailColor ?? this.disabledActiveRailColor,
+      disabledInactiveRailColor: disabledInactiveRailColor ?? this.disabledInactiveRailColor,
+      activeTickMarkColor: activeTickMarkColor ?? this.activeTickMarkColor,
+      inactiveTickMarkColor: inactiveTickMarkColor ?? this.inactiveTickMarkColor,
+      disabledActiveTickMarkColor: disabledActiveTickMarkColor ?? this.disabledActiveTickMarkColor,
+      disabledInactiveTickMarkColor:
+          disabledInactiveTickMarkColor ?? this.disabledInactiveTickMarkColor,
+      thumbColor: thumbColor ?? this.thumbColor,
+      disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor,
+      overlayColor: overlayColor ?? this.overlayColor,
+      valueIndicatorColor: valueIndicatorColor ?? this.valueIndicatorColor,
+      thumbShape: thumbShape ?? this.thumbShape,
+      valueIndicatorShape: valueIndicatorShape ?? this.valueIndicatorShape,
+      showValueIndicator: showValueIndicator ?? this.showValueIndicator,
+    );
+  }
+
+  /// Linearly interpolate between two slider themes.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static SliderThemeData lerp(SliderThemeData a, SliderThemeData b, double t) {
+    assert(a != null);
+    assert(b != null);
+    assert(t != null);
+    return new SliderThemeData(
+      activeRailColor: Color.lerp(a.activeRailColor, b.activeRailColor, t),
+      inactiveRailColor: Color.lerp(a.inactiveRailColor, b.inactiveRailColor, t),
+      disabledActiveRailColor: Color.lerp(a.disabledActiveRailColor, b.disabledActiveRailColor, t),
+      disabledInactiveRailColor:
+          Color.lerp(a.disabledInactiveRailColor, b.disabledInactiveRailColor, t),
+      activeTickMarkColor: Color.lerp(a.activeTickMarkColor, b.activeTickMarkColor, t),
+      inactiveTickMarkColor: Color.lerp(a.inactiveTickMarkColor, b.inactiveTickMarkColor, t),
+      disabledActiveTickMarkColor:
+          Color.lerp(a.disabledActiveTickMarkColor, b.disabledActiveTickMarkColor, t),
+      disabledInactiveTickMarkColor:
+          Color.lerp(a.disabledInactiveTickMarkColor, b.disabledInactiveTickMarkColor, t),
+      thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t),
+      disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t),
+      overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t),
+      valueIndicatorColor: Color.lerp(a.valueIndicatorColor, b.valueIndicatorColor, t),
+      thumbShape: t < 0.5 ? a.thumbShape : b.thumbShape,
+      valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape,
+      showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      activeRailColor,
+      inactiveRailColor,
+      disabledActiveRailColor,
+      disabledInactiveRailColor,
+      activeTickMarkColor,
+      inactiveTickMarkColor,
+      disabledActiveTickMarkColor,
+      disabledInactiveTickMarkColor,
+      thumbColor,
+      disabledThumbColor,
+      overlayColor,
+      valueIndicatorColor,
+      thumbShape,
+      valueIndicatorShape,
+      showValueIndicator,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SliderThemeData otherData = other;
+    return otherData.activeRailColor == activeRailColor &&
+        otherData.inactiveRailColor == inactiveRailColor &&
+        otherData.disabledActiveRailColor == disabledActiveRailColor &&
+        otherData.disabledInactiveRailColor == disabledInactiveRailColor &&
+        otherData.activeTickMarkColor == activeTickMarkColor &&
+        otherData.inactiveTickMarkColor == inactiveTickMarkColor &&
+        otherData.disabledActiveTickMarkColor == disabledActiveTickMarkColor &&
+        otherData.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor &&
+        otherData.thumbColor == thumbColor &&
+        otherData.disabledThumbColor == disabledThumbColor &&
+        otherData.overlayColor == overlayColor &&
+        otherData.valueIndicatorColor == valueIndicatorColor &&
+        otherData.thumbShape == thumbShape &&
+        otherData.valueIndicatorShape == valueIndicatorShape &&
+        otherData.showValueIndicator == showValueIndicator;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    final ThemeData defaultTheme = new ThemeData.fallback();
+    final SliderThemeData defaultData = new SliderThemeData.materialDefaults(
+      primaryColor: defaultTheme.primaryColor,
+      primaryColorDark: defaultTheme.primaryColorDark,
+      primaryColorLight: defaultTheme.primaryColorLight,
+    );
+    description.add(new DiagnosticsProperty<Color>('activeRailColor', activeRailColor,
+        defaultValue: defaultData.activeRailColor));
+    description.add(new DiagnosticsProperty<Color>('inactiveRailColor', inactiveRailColor,
+        defaultValue: defaultData.inactiveRailColor));
+    description.add(new DiagnosticsProperty<Color>(
+        'disabledActiveRailColor', disabledActiveRailColor,
+        defaultValue: defaultData.disabledActiveRailColor));
+    description.add(new DiagnosticsProperty<Color>(
+        'disabledInactiveRailColor', disabledInactiveRailColor,
+        defaultValue: defaultData.disabledInactiveRailColor));
+    description.add(new DiagnosticsProperty<Color>('activeTickMarkColor', activeTickMarkColor,
+        defaultValue: defaultData.activeTickMarkColor));
+    description.add(new DiagnosticsProperty<Color>('inactiveTickMarkColor', inactiveTickMarkColor,
+        defaultValue: defaultData.inactiveTickMarkColor));
+    description.add(new DiagnosticsProperty<Color>(
+        'disabledActiveTickMarkColor', disabledActiveTickMarkColor,
+        defaultValue: defaultData.disabledActiveTickMarkColor));
+    description.add(new DiagnosticsProperty<Color>(
+        'disabledInactiveTickMarkColor', disabledInactiveTickMarkColor,
+        defaultValue: defaultData.disabledInactiveTickMarkColor));
+    description.add(new DiagnosticsProperty<Color>('thumbColor', thumbColor,
+        defaultValue: defaultData.thumbColor));
+    description.add(new DiagnosticsProperty<Color>('disabledThumbColor', disabledThumbColor,
+        defaultValue: defaultData.disabledThumbColor));
+    description.add(new DiagnosticsProperty<Color>('overlayColor', overlayColor,
+        defaultValue: defaultData.overlayColor));
+    description.add(new DiagnosticsProperty<Color>('valueIndicatorColor', valueIndicatorColor,
+        defaultValue: defaultData.valueIndicatorColor));
+    description.add(new DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape,
+        defaultValue: defaultData.thumbShape));
+    description.add(new DiagnosticsProperty<SliderComponentShape>(
+        'valueIndicatorShape', valueIndicatorShape,
+        defaultValue: defaultData.valueIndicatorShape));
+    description.add(new DiagnosticsProperty<ShowValueIndicator>(
+        'showValueIndicator', showValueIndicator,
+        defaultValue: defaultData.showValueIndicator));
+  }
+}
+
+/// Base class for slider thumb and value indicator shapes.
+///
+/// Create a subclass of this if you would like a custom slider thumb or
+/// value indicator shape.
+///
+/// See also:
+///
+///  * [RoundSliderThumbShape] for a simple example of a thumb shape.
+///  * [PaddleSliderValueIndicatorShape], for a complex example of a value
+///    indicator shape.
+abstract class SliderComponentShape {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliderComponentShape();
+
+  /// Returns the preferred size of the shape, based on the given conditions.
+  Size getPreferredSize(bool isEnabled, bool isDiscrete);
+
+  /// Paints the shape, taking into account the state passed to it.
+  ///
+  /// [activationAnimation] is an animation triggered when the user beings
+  /// to interact with the slider. It reverses when the user stops interacting
+  /// with the slider.
+  /// [enableAnimation] is an animation triggered when the [Slider] is enabled,
+  /// and it reverses when the slider is disabled.
+  /// If [labelPainter] is non-null, then [labelPainter.paint] should be
+  /// called with the location that the label should appear. If the labelPainter
+  /// passed is null, then no label was supplied to the [Slider].
+  /// [value] is the current parametric value (from 0.0 to 1.0) of the slider.
+  void paint(
+    PaintingContext context,
+    bool isDiscrete,
+    Offset thumbCenter,
+    Animation<double> activationAnimation,
+    Animation<double> enableAnimation,
+    TextPainter labelPainter,
+    SliderThemeData sliderTheme,
+    TextDirection textDirection,
+    double textScaleFactor,
+    double value,
+  );
+}
+
+/// This is the default shape to a [Slider]'s thumb if no
+/// other shape is specified.
+///
+/// See also:
+///
+///  * [Slider] for the component that this is meant to display this shape.
+///  * [SliderThemeData] where an instance of this class is set to inform the
+///    slider of the shape of the its thumb.
+class RoundSliderThumbShape extends SliderComponentShape {
+  const RoundSliderThumbShape();
+  static const double _thumbRadius = 6.0;
+  static const double _disabledThumbRadius = 4.0;
+
+  @override
+  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+    return new Size.fromRadius(isEnabled ? _thumbRadius : _disabledThumbRadius);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    bool isDiscrete,
+    Offset thumbCenter,
+    Animation<double> activationAnimation,
+    Animation<double> enableAnimation,
+    TextPainter labelPainter,
+    SliderThemeData sliderTheme,
+    TextDirection textDirection,
+    double textScaleFactor,
+    double value,
+  ) {
+    final Canvas canvas = context.canvas;
+    final Tween<double> radiusTween =
+        new Tween<double>(begin: _disabledThumbRadius, end: _thumbRadius);
+    final ColorTween colorTween =
+        new ColorTween(begin: sliderTheme.disabledThumbColor, end: sliderTheme.thumbColor);
+    canvas.drawCircle(
+      thumbCenter,
+      radiusTween.evaluate(enableAnimation),
+      new Paint()..color = colorTween.evaluate(enableAnimation),
+    );
+  }
+}
+
+/// This is the default shape to a [Slider]'s value indicator if no
+/// other shape is specified.
+///
+/// See also:
+///
+///  * [Slider] for the component that this is meant to display this shape.
+///  * [SliderThemeData] where an instance of this class is set to inform the
+///    slider of the shape of the its value indicator.
+class PaddleSliderValueIndicatorShape extends SliderComponentShape {
+  const PaddleSliderValueIndicatorShape();
+
+  // These constants define the shape of the default value indicator.
+  // The value indicator changes shape based on the size of
+  // the label: The top lobe spreads horizontally, and the
+  // top arc on the neck moves down to keep it merging smoothly
+  // with the top lobe as it expands.
+
+  // Radius of the top lobe of the value indicator.
+  static const double _topLobeRadius = 16.0;
+  // Radius of the bottom lobe of the value indicator.
+  static const double _bottomLobeRadius = 6.0;
+  // The starting angle for the bottom lobe. Picked to get the desired
+  // thickness for the neck.
+  static const double _bottomLobeStartAngle = -1.1 * math.pi / 4.0;
+  // The ending angle for the bottom lobe. Picked to get the desired
+  // thickness for the neck.
+  static const double _bottomLobeEndAngle = 1.1 * 5 * math.pi / 4.0;
+  // The padding on either side of the label.
+  static const double _labelPadding = 8.0;
+  static const double _distanceBetweenTopBottomCenters = 40.0;
+  static const Offset _topLobeCenter = const Offset(0.0, -_distanceBetweenTopBottomCenters);
+  static const double _topNeckRadius = 14.0;
+  // The length of the hypotenuse of the triangle formed by the center
+  // of the left top lobe arc and the center of the top left neck arc.
+  // Used to calculate the position of the center of the arc.
+  static const double _neckTriangleHypotenuse = _topLobeRadius + _topNeckRadius;
+  // Some convenience values to help readability.
+  static const double _twoSeventyDegrees = 3.0 * math.pi / 2.0;
+  static const double _ninetyDegrees = math.pi / 2.0;
+  static const double _thirtyDegrees = math.pi / 6.0;
+  static const Size preferredSize =
+      const Size.fromHeight(_distanceBetweenTopBottomCenters + _topLobeRadius + _bottomLobeRadius);
+
+  static final Tween<double> _slideUpTween = new Tween<double>(begin: 0.0, end: 1.0);
+  static Path _bottomLobePath; // Initialized by _generateBottomLobe
+  static Offset _bottomLobeEnd; // Initialized by _generateBottomLobe
+
+  @override
+  Size getPreferredSize(bool isEnabled, bool isDiscrete) => preferredSize;
+
+  // Adds an arc to the path that has the attributes passed in. This is
+  // a convenience to make adding arcs have less boilerplate.
+  static void _addArc(Path path, Offset center, double radius, double startAngle, double endAngle) {
+    final Rect arcRect = new Rect.fromCircle(center: center, radius: radius);
+    path.arcTo(arcRect, startAngle, endAngle - startAngle, false);
+  }
+
+  // Generates the bottom lobe path, which is the same for all instances of
+  // the value indicator, so we reuse it for each one.
+  static void _generateBottomLobe() {
+    const double bottomNeckRadius = 4.5;
+    const double bottomNeckStartAngle = _bottomLobeEndAngle - math.pi;
+    const double bottomNeckEndAngle = 0.0;
+
+    final Path path = new Path();
+    final Offset bottomKnobStart = new Offset(
+      _bottomLobeRadius * math.cos(_bottomLobeStartAngle),
+      _bottomLobeRadius * math.sin(_bottomLobeStartAngle),
+    );
+    final Offset bottomNeckRightCenter = bottomKnobStart +
+        new Offset(
+          bottomNeckRadius * math.cos(bottomNeckStartAngle),
+          -bottomNeckRadius * math.sin(bottomNeckStartAngle),
+        );
+    final Offset bottomNeckLeftCenter = new Offset(
+      -bottomNeckRightCenter.dx,
+      bottomNeckRightCenter.dy,
+    );
+    final Offset bottomNeckStartRight = new Offset(
+      bottomNeckRightCenter.dx - bottomNeckRadius,
+      bottomNeckRightCenter.dy,
+    );
+    path.moveTo(bottomNeckStartRight.dx, bottomNeckStartRight.dy);
+    _addArc(
+      path,
+      bottomNeckRightCenter,
+      bottomNeckRadius,
+      math.pi - bottomNeckEndAngle,
+      math.pi - bottomNeckStartAngle,
+    );
+    _addArc(
+      path,
+      Offset.zero,
+      _bottomLobeRadius,
+      _bottomLobeStartAngle,
+      _bottomLobeEndAngle,
+    );
+    _addArc(
+      path,
+      bottomNeckLeftCenter,
+      bottomNeckRadius,
+      bottomNeckStartAngle,
+      bottomNeckEndAngle,
+    );
+
+    _bottomLobeEnd = new Offset(
+      -bottomNeckStartRight.dx,
+      bottomNeckStartRight.dy,
+    );
+    _bottomLobePath = path;
+  }
+
+  Offset _addBottomLobe(Path path) {
+    if (_bottomLobePath == null || _bottomLobeEnd == null) {
+      // Generate this lazily so as to not slow down app startup.
+      _generateBottomLobe();
+    }
+    path.extendWithPath(_bottomLobePath, Offset.zero);
+    return _bottomLobeEnd;
+  }
+
+  void _drawValueIndicator(Canvas canvas, Offset center, Paint paint, double scale,
+      TextPainter labelPainter, double textScaleFactor) {
+    canvas.save();
+    canvas.translate(center.dx, center.dy);
+    // The entire value indicator should scale with the text scale factor,
+    // to keep it large enough to encompass the label text.
+    canvas.scale(scale * textScaleFactor, scale * textScaleFactor);
+    final double inverseTextScale = 1.0 / textScaleFactor;
+    final double labelHalfWidth = labelPainter.width / 2.0;
+
+    // This is the needed extra width for the label.  It is only positive when
+    // the label exceeds the minimum size contained by the round top lobe.
+    final double halfWidthNeeded =
+        math.max(0.0, inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding));
+
+    final Path path = new Path();
+    final Offset bottomLobeEnd = _addBottomLobe(path);
+    // The base of the triangle between the top lobe center and the centers of
+    // the two top neck arcs.
+    final double neckTriangleBase = _topNeckRadius - bottomLobeEnd.dx;
+    // The parameter that describes how far along the transition from round to
+    // stretched we are.
+    final double t = math.max(0.0, math.min(1.0, halfWidthNeeded / neckTriangleBase));
+    // The angle between the top neck arc's center and the top lobe's center
+    // and vertical.
+    final double theta = (1.0 - t) * _thirtyDegrees;
+    // The center of the top left neck arc.
+    final Offset neckLeftCenter = new Offset(
+        -neckTriangleBase, _topLobeCenter.dy + math.cos(theta) * _neckTriangleHypotenuse);
+    final Offset topLobeShift = new Offset(halfWidthNeeded, 0.0);
+    final double neckArcAngle = _ninetyDegrees - theta;
+    _addArc(
+      path,
+      neckLeftCenter,
+      _topNeckRadius,
+      0.0,
+      -neckArcAngle,
+    );
+    _addArc(path, _topLobeCenter - topLobeShift, _topLobeRadius, _ninetyDegrees + theta,
+        _twoSeventyDegrees);
+    _addArc(path, _topLobeCenter + topLobeShift, _topLobeRadius, _twoSeventyDegrees,
+        _twoSeventyDegrees + math.pi - theta);
+    final Offset neckRightCenter = new Offset(-neckLeftCenter.dx, neckLeftCenter.dy);
+    _addArc(
+      path,
+      neckRightCenter,
+      _topNeckRadius,
+      math.pi + neckArcAngle,
+      math.pi,
+    );
+    canvas.drawPath(path, paint);
+
+    // Draw the label.
+    canvas.save();
+    canvas.translate(0.0, -_distanceBetweenTopBottomCenters);
+    canvas.scale(inverseTextScale, inverseTextScale);
+    labelPainter.paint(canvas, Offset.zero - new Offset(labelHalfWidth, labelPainter.height / 2.0));
+    canvas.restore();
+    canvas.restore();
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    bool isDiscrete,
+    Offset thumbCenter,
+    Animation<double> activationAnimation,
+    Animation<double> enableAnimation,
+    TextPainter labelPainter,
+    SliderThemeData sliderTheme,
+    TextDirection textDirection,
+    double textScaleFactor,
+    double value,
+  ) {
+    assert(labelPainter != null);
+    final ColorTween colorTween =
+        new ColorTween(begin: Colors.transparent, end: sliderTheme.valueIndicatorColor);
+    final ColorTween enableColor = new ColorTween(
+        begin: sliderTheme.disabledThumbColor, end: colorTween.evaluate(activationAnimation));
+    _drawValueIndicator(
+      context.canvas,
+      thumbCenter,
+      new Paint()..color = enableColor.evaluate(enableAnimation),
+      _slideUpTween.evaluate(activationAnimation),
+      labelPainter,
+      textScaleFactor,
+    );
+  }
+}
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index 0949c51..318151a 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -12,6 +12,7 @@
 import 'ink_splash.dart';
 import 'ink_well.dart' show InteractiveInkFeatureFactory;
 import 'input_decorator.dart';
+import 'slider_theme.dart';
 import 'typography.dart';
 
 /// Describes the contrast needs of a color.
@@ -52,8 +53,8 @@
 ///
 /// To obtain the current theme, use [Theme.of].
 @immutable
-class ThemeData {
-  /// Create a ThemeData given a set of preferred values.
+class ThemeData extends Diagnosticable {
+  /// Create a [ThemeData] given a set of preferred values.
   ///
   /// Default values will be derived for arguments that are omitted.
   ///
@@ -78,6 +79,8 @@
     MaterialColor primarySwatch,
     Color primaryColor,
     Brightness primaryColorBrightness,
+    Color primaryColorLight,
+    Color primaryColorDark,
     Color accentColor,
     Brightness accentColorBrightness,
     Color canvasColor,
@@ -109,13 +112,16 @@
     IconThemeData iconTheme,
     IconThemeData primaryIconTheme,
     IconThemeData accentIconTheme,
-    TargetPlatform platform
+    SliderThemeData sliderTheme,
+    TargetPlatform platform,
   }) {
     brightness ??= Brightness.light;
     final bool isDark = brightness == Brightness.dark;
     primarySwatch ??= Colors.blue;
     primaryColor ??= isDark ? Colors.grey[900] : primarySwatch[500];
     primaryColorBrightness ??= estimateBrightnessForColor(primaryColor);
+    primaryColorLight ??= isDark ? Colors.grey[500] : primarySwatch[100];
+    primaryColorDark ??= isDark ? Colors.black : primarySwatch[700];
     final bool primaryIsDark = primaryColorBrightness == Brightness.dark;
     accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500];
     accentColorBrightness ??= estimateBrightnessForColor(accentColor);
@@ -143,9 +149,15 @@
     hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000);
     errorColor ??= Colors.red[700];
     inputDecorationTheme ??= const InputDecorationTheme();
-    iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
-    primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
-    accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
+    iconTheme ??= isDark
+        ? const IconThemeData(color: Colors.white)
+        : const IconThemeData(color: Colors.black);
+    primaryIconTheme ??= primaryIsDark
+        ? const IconThemeData(color: Colors.white)
+        : const IconThemeData(color: Colors.black);
+    accentIconTheme ??= accentIsDark
+        ? const IconThemeData(color: Colors.white)
+        : const IconThemeData(color: Colors.black);
     platform ??= defaultTargetPlatform;
     final Typography typography = new Typography(platform: platform);
     textTheme ??= isDark ? typography.white : typography.black;
@@ -156,10 +168,17 @@
       primaryTextTheme = primaryTextTheme.apply(fontFamily: fontFamily);
       accentTextTheme = accentTextTheme.apply(fontFamily: fontFamily);
     }
+    sliderTheme ??= new SliderThemeData.materialDefaults(
+      primaryColor: primaryColor,
+      primaryColorLight: primaryColorLight,
+      primaryColorDark: primaryColorDark,
+    );
     return new ThemeData.raw(
       brightness: brightness,
       primaryColor: primaryColor,
       primaryColorBrightness: primaryColorBrightness,
+      primaryColorLight: primaryColorLight,
+      primaryColorDark: primaryColorDark,
       accentColor: accentColor,
       accentColorBrightness: accentColorBrightness,
       canvasColor: canvasColor,
@@ -190,11 +209,12 @@
       iconTheme: iconTheme,
       primaryIconTheme: primaryIconTheme,
       accentIconTheme: accentIconTheme,
-      platform: platform
+      sliderTheme: sliderTheme,
+      platform: platform,
     );
   }
 
-  /// Create a ThemeData given a set of exact values. All the values
+  /// Create a [ThemeData] given a set of exact values. All the values
   /// must be specified.
   ///
   /// This will rarely be used directly. It is used by [lerp] to
@@ -204,6 +224,8 @@
     @required this.brightness,
     @required this.primaryColor,
     @required this.primaryColorBrightness,
+    @required this.primaryColorLight,
+    @required this.primaryColorDark,
     @required this.accentColor,
     @required this.accentColorBrightness,
     @required this.canvasColor,
@@ -234,10 +256,13 @@
     @required this.iconTheme,
     @required this.primaryIconTheme,
     @required this.accentIconTheme,
-    @required this.platform
+    @required this.sliderTheme,
+    @required this.platform,
   }) : assert(brightness != null),
        assert(primaryColor != null),
        assert(primaryColorBrightness != null),
+       assert(primaryColorLight != null),
+       assert(primaryColorDark != null),
        assert(accentColor != null),
        assert(accentColorBrightness != null),
        assert(canvasColor != null),
@@ -267,6 +292,7 @@
        assert(iconTheme != null),
        assert(primaryIconTheme != null),
        assert(accentIconTheme != null),
+       assert(sliderTheme != null),
        assert(platform != null);
 
   /// A default light blue theme.
@@ -311,6 +337,12 @@
   /// icons placed on top of the primary color (e.g. toolbar text).
   final Brightness primaryColorBrightness;
 
+  /// A lighter version of the [primaryColor].
+  final Color primaryColorLight;
+
+  /// A darker version of the [primaryColor].
+  final Color primaryColorDark;
+
   /// The foreground color for widgets (knobs, text, overscroll edge effect, etc).
   final Color accentColor;
 
@@ -352,7 +384,7 @@
   ///
   ///  * [InkSplash.splashFactory], which defines the default splash.
   ///  * [InkRipple.splashFactory], which defines a splash that spreads out
-  ///    more aggresively than the default.
+  ///    more aggressively than the default.
   final InteractiveInkFeatureFactory splashFactory;
 
   /// The color used to highlight selected rows.
@@ -428,6 +460,11 @@
   /// An icon theme that contrasts with the accent color.
   final IconThemeData accentIconTheme;
 
+  /// The colors and shapes used to render [Slider].
+  ///
+  /// This is the value returned from [SliderTheme.of].
+  final SliderThemeData sliderTheme;
+
   /// The platform the material widgets should adapt to target.
   ///
   /// Defaults to the current platform.
@@ -438,6 +475,8 @@
     Brightness brightness,
     Color primaryColor,
     Brightness primaryColorBrightness,
+    Color primaryColorLight,
+    Color primaryColorDark,
     Color accentColor,
     Brightness accentColorBrightness,
     Color canvasColor,
@@ -468,12 +507,15 @@
     IconThemeData iconTheme,
     IconThemeData primaryIconTheme,
     IconThemeData accentIconTheme,
+    SliderThemeData sliderTheme,
     TargetPlatform platform,
   }) {
     return new ThemeData.raw(
       brightness: brightness ?? this.brightness,
       primaryColor: primaryColor ?? this.primaryColor,
       primaryColorBrightness: primaryColorBrightness ?? this.primaryColorBrightness,
+      primaryColorLight: primaryColorLight ?? this.primaryColorLight,
+      primaryColorDark: primaryColorDark ?? this.primaryColorDark,
       accentColor: accentColor ?? this.accentColor,
       accentColorBrightness: accentColorBrightness ?? this.accentColorBrightness,
       canvasColor: canvasColor ?? this.canvasColor,
@@ -504,6 +546,7 @@
       iconTheme: iconTheme ?? this.iconTheme,
       primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
       accentIconTheme: accentIconTheme ?? this.accentIconTheme,
+      sliderTheme: sliderTheme ?? this.sliderTheme,
       platform: platform ?? this.platform,
     );
   }
@@ -515,7 +558,8 @@
   static const int _localizedThemeDataCacheSize = 5;
 
   /// Caches localized themes to speed up the [localize] method.
-  static final _FifoCache<_IdentityThemeDataCacheKey, ThemeData> _localizedThemeDataCache = new _FifoCache<_IdentityThemeDataCacheKey, ThemeData>(_localizedThemeDataCacheSize);
+  static final _FifoCache<_IdentityThemeDataCacheKey, ThemeData> _localizedThemeDataCache =
+      new _FifoCache<_IdentityThemeDataCacheKey, ThemeData>(_localizedThemeDataCacheSize);
 
   /// Returns a new theme built by merging the text geometry provided by the
   /// [localTextGeometry] theme with the [baseTheme].
@@ -567,7 +611,7 @@
     // Design spec shows for its color palette on
     // <https://material.io/guidelines/style/color.html#color-color-palette>.
     const double kThreshold = 0.15;
-    if ((relativeLuminance + 0.05) * (relativeLuminance + 0.05) > kThreshold )
+    if ((relativeLuminance + 0.05) * (relativeLuminance + 0.05) > kThreshold)
       return Brightness.light;
     return Brightness.dark;
   }
@@ -595,6 +639,8 @@
       brightness: t < 0.5 ? a.brightness : b.brightness,
       primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t),
       primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness,
+      primaryColorLight: Color.lerp(a.primaryColorLight, b.primaryColorLight, t),
+      primaryColorDark: Color.lerp(a.primaryColorDark, b.primaryColorDark, t),
       canvasColor: Color.lerp(a.canvasColor, b.canvasColor, t),
       scaffoldBackgroundColor: Color.lerp(a.scaffoldBackgroundColor, b.scaffoldBackgroundColor, t),
       bottomAppBarColor: Color.lerp(a.bottomAppBarColor, b.bottomAppBarColor, t),
@@ -625,6 +671,7 @@
       iconTheme: IconThemeData.lerp(a.iconTheme, b.iconTheme, t),
       primaryIconTheme: IconThemeData.lerp(a.primaryIconTheme, b.primaryIconTheme, t),
       accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
+      sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
       platform: t < 0.5 ? a.platform : b.platform,
     );
   }
@@ -667,53 +714,120 @@
            (otherData.iconTheme == iconTheme) &&
            (otherData.primaryIconTheme == primaryIconTheme) &&
            (otherData.accentIconTheme == accentIconTheme) &&
+           (otherData.sliderTheme == sliderTheme) &&
            (otherData.platform == platform);
   }
 
   @override
   int get hashCode {
     return hashValues(
-      brightness,
-      primaryColor,
-      primaryColorBrightness,
-      canvasColor,
-      scaffoldBackgroundColor,
-      bottomAppBarColor,
-      cardColor,
-      dividerColor,
-      highlightColor,
-      splashColor,
-      splashFactory,
-      selectedRowColor,
-      unselectedWidgetColor,
-      disabledColor,
-      buttonColor,
-      buttonTheme,
-      secondaryHeaderColor,
-      textSelectionColor,
-      textSelectionHandleColor,
-      hashValues( // Too many values.
-        backgroundColor,
-        accentColor,
-        accentColorBrightness,
-        indicatorColor,
-        dialogBackgroundColor,
-        hintColor,
-        errorColor,
-        textTheme,
-        primaryTextTheme,
-        accentTextTheme,
-        iconTheme,
-        inputDecorationTheme,
-        primaryIconTheme,
-        accentIconTheme,
-        platform,
-      )
+        brightness,
+        primaryColor,
+        primaryColorBrightness,
+        canvasColor,
+        scaffoldBackgroundColor,
+        bottomAppBarColor,
+        cardColor,
+        dividerColor,
+        highlightColor,
+        splashColor,
+        splashFactory,
+        selectedRowColor,
+        unselectedWidgetColor,
+        disabledColor,
+        buttonColor,
+        buttonTheme,
+        secondaryHeaderColor,
+        textSelectionColor,
+        textSelectionHandleColor,
+        hashValues( // Too many values.
+          backgroundColor,
+          accentColor,
+          accentColorBrightness,
+          indicatorColor,
+          dialogBackgroundColor,
+          hintColor,
+          errorColor,
+          textTheme,
+          primaryTextTheme,
+          accentTextTheme,
+          iconTheme,
+          inputDecorationTheme,
+          primaryIconTheme,
+          accentIconTheme,
+          sliderTheme,
+          platform,
+        ),
     );
   }
 
   @override
-  String toString() => '$runtimeType(${ platform != defaultTargetPlatform ? "$platform " : ''}$brightness $primaryColor etc...)';
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    final ThemeData defaultData = new ThemeData.fallback();
+    description.add(new EnumProperty<TargetPlatform>('platform', platform,
+        defaultValue: defaultTargetPlatform));
+    description.add(new EnumProperty<Brightness>('brightness', brightness,
+        defaultValue: defaultData.brightness));
+    description.add(new DiagnosticsProperty<Color>('primaryColor', primaryColor,
+        defaultValue: defaultData.primaryColor));
+    description.add(new EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness,
+        defaultValue: defaultData.primaryColorBrightness));
+    description.add(new DiagnosticsProperty<Color>('accentColor', accentColor,
+        defaultValue: defaultData.accentColor));
+    description.add(new EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness,
+        defaultValue: defaultData.accentColorBrightness));
+    description.add(new DiagnosticsProperty<Color>('canvasColor', canvasColor,
+        defaultValue: defaultData.canvasColor));
+    description.add(new DiagnosticsProperty<Color>(
+        'scaffoldBackgroundColor', scaffoldBackgroundColor,
+        defaultValue: defaultData.scaffoldBackgroundColor));
+    description.add(new DiagnosticsProperty<Color>('bottomAppBarColor', bottomAppBarColor,
+        defaultValue: defaultData.bottomAppBarColor));
+    description.add(new DiagnosticsProperty<Color>('cardColor', cardColor,
+        defaultValue: defaultData.cardColor));
+    description.add(new DiagnosticsProperty<Color>('dividerColor', dividerColor,
+        defaultValue: defaultData.dividerColor));
+    description.add(new DiagnosticsProperty<Color>('highlightColor', highlightColor,
+        defaultValue: defaultData.highlightColor));
+    description.add(new DiagnosticsProperty<Color>('splashColor', splashColor,
+        defaultValue: defaultData.splashColor));
+    description.add(new DiagnosticsProperty<Color>('selectedRowColor', selectedRowColor,
+        defaultValue: defaultData.selectedRowColor));
+    description.add(new DiagnosticsProperty<Color>('unselectedWidgetColor', unselectedWidgetColor,
+        defaultValue: defaultData.unselectedWidgetColor));
+    description.add(new DiagnosticsProperty<Color>('disabledColor', disabledColor,
+        defaultValue: defaultData.disabledColor));
+    description.add(new DiagnosticsProperty<Color>('buttonColor', buttonColor,
+        defaultValue: defaultData.buttonColor));
+    description.add(new DiagnosticsProperty<Color>('secondaryHeaderColor', secondaryHeaderColor,
+        defaultValue: defaultData.secondaryHeaderColor));
+    description.add(new DiagnosticsProperty<Color>('textSelectionColor', textSelectionColor,
+        defaultValue: defaultData.textSelectionColor));
+    description.add(new DiagnosticsProperty<Color>(
+        'textSelectionHandleColor', textSelectionHandleColor,
+        defaultValue: defaultData.textSelectionHandleColor));
+    description.add(new DiagnosticsProperty<Color>('backgroundColor', backgroundColor,
+        defaultValue: defaultData.backgroundColor));
+    description.add(new DiagnosticsProperty<Color>('dialogBackgroundColor', dialogBackgroundColor,
+        defaultValue: defaultData.dialogBackgroundColor));
+    description.add(new DiagnosticsProperty<Color>('indicatorColor', indicatorColor,
+        defaultValue: defaultData.indicatorColor));
+    description.add(new DiagnosticsProperty<Color>('hintColor', hintColor,
+        defaultValue: defaultData.hintColor));
+    description.add(new DiagnosticsProperty<Color>('errorColor', errorColor,
+        defaultValue: defaultData.errorColor));
+    description.add(new DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme));
+    description.add(new DiagnosticsProperty<TextTheme>('textTheme', textTheme));
+    description.add(new DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme));
+    description.add(new DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme));
+    description.add(new DiagnosticsProperty<InputDecorationTheme>(
+        'inputDecorationTheme', inputDecorationTheme));
+    description.add(new DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme));
+    description.add(new DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme));
+    description.add(new DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
+    description.add(new DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
+  }
 }
 
 class _IdentityThemeDataCacheKey {
@@ -732,7 +846,8 @@
     // We are explicitly ignoring the possibility that the types might not
     // match in the interests of speed.
     final _IdentityThemeDataCacheKey otherKey = other;
-    return identical(baseTheme, otherKey.baseTheme) && identical(localTextGeometry, otherKey.localTextGeometry);
+    return identical(baseTheme, otherKey.baseTheme) &&
+        identical(localTextGeometry, otherKey.localTextGeometry);
   }
 }
 
@@ -742,8 +857,7 @@
 /// The key that was inserted before all other keys is evicted first, i.e. the
 /// one inserted least recently.
 class _FifoCache<K, V> {
-  _FifoCache(this._maximumSize)
-    : assert(_maximumSize != null && _maximumSize > 0);
+  _FifoCache(this._maximumSize) : assert(_maximumSize != null && _maximumSize > 0);
 
   /// In Dart the map literal uses a linked hash-map implementation, whose keys
   /// are stored such that [Map.keys] returns them in the order they were
diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart
index 2e131af..80b0f32 100644
--- a/packages/flutter/lib/src/material/typography.dart
+++ b/packages/flutter/lib/src/material/typography.dart
@@ -33,7 +33,7 @@
 ///    globally adjusted, such as the color scheme.
 ///  * <http://material.google.com/style/typography.html>
 @immutable
-class TextTheme {
+class TextTheme extends Diagnosticable {
   /// Creates a text theme that uses the given values.
   ///
   /// Rather than creating a new text theme, consider using [Typography.black]
@@ -114,7 +114,7 @@
     TextStyle body2,
     TextStyle body1,
     TextStyle caption,
-    TextStyle button
+    TextStyle button,
   }) {
     return new TextTheme(
       display4: display4 ?? this.display4,
@@ -353,6 +353,34 @@
       button,
     );
   }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    final TextTheme defaultTheme = new Typography(platform: defaultTargetPlatform).black;
+    description.add(new DiagnosticsProperty<TextStyle>('display4', display4,
+        defaultValue: defaultTheme.display4));
+    description.add(new DiagnosticsProperty<TextStyle>('display3', display3,
+        defaultValue: defaultTheme.display3));
+    description.add(new DiagnosticsProperty<TextStyle>('display2', display2,
+        defaultValue: defaultTheme.display2));
+    description.add(new DiagnosticsProperty<TextStyle>('display1', display1,
+        defaultValue: defaultTheme.display1));
+    description.add(new DiagnosticsProperty<TextStyle>('headline', headline,
+        defaultValue: defaultTheme.headline));
+    description
+        .add(new DiagnosticsProperty<TextStyle>('title', title, defaultValue: defaultTheme.title));
+    description.add(
+        new DiagnosticsProperty<TextStyle>('subhead', subhead, defaultValue: defaultTheme.subhead));
+    description
+        .add(new DiagnosticsProperty<TextStyle>('body2', body2, defaultValue: defaultTheme.body2));
+    description
+        .add(new DiagnosticsProperty<TextStyle>('body1', body1, defaultValue: defaultTheme.body1));
+    description.add(
+        new DiagnosticsProperty<TextStyle>('caption', caption, defaultValue: defaultTheme.caption));
+    description.add(
+        new DiagnosticsProperty<TextStyle>('button', button, defaultValue: defaultTheme.button));
+  }
 }
 
 /// The two material design text themes.
@@ -373,7 +401,7 @@
 ///  * <http://material.google.com/style/typography.html>
 class Typography {
   /// Creates the default typography for the specified platform.
-  factory Typography({ @required TargetPlatform platform }) {
+  factory Typography({@required TargetPlatform platform}) {
     assert(platform != null);
     switch (platform) {
       case TargetPlatform.android:
diff --git a/packages/flutter/lib/src/widgets/icon_theme_data.dart b/packages/flutter/lib/src/widgets/icon_theme_data.dart
index 0dc5f41..e13aea9 100644
--- a/packages/flutter/lib/src/widgets/icon_theme_data.dart
+++ b/packages/flutter/lib/src/widgets/icon_theme_data.dart
@@ -5,6 +5,8 @@
 import 'dart:ui' show Color, hashValues;
 import 'dart:ui' as ui show lerpDouble;
 
+import 'package:flutter/foundation.dart';
+
 /// Defines the color, opacity, and size of icons.
 ///
 /// Used by [IconTheme] to control the color, opacity, and size of icons in a
@@ -13,29 +15,26 @@
 /// To obtain the current icon theme, use [IconTheme.of]. To convert an icon
 /// theme to a version with all the fields filled in, use [new
 /// IconThemeData.fallback].
-class IconThemeData {
+class IconThemeData extends Diagnosticable {
   /// Creates an icon theme data.
   ///
   /// The opacity applies to both explicit and default icon colors. The value
   /// is clamped between 0.0 and 1.0.
-  const IconThemeData({ this.color, double opacity, this.size }) : _opacity = opacity;
+  const IconThemeData({this.color, double opacity, this.size}) : _opacity = opacity;
 
   /// Creates an icon them with some reasonable default values.
   ///
   /// The [color] is black, the [opacity] is 1.0, and the [size] is 24.0.
   const IconThemeData.fallback()
-    : color = const Color(0xFF000000),
-      _opacity = 1.0,
-      size = 24.0;
+      : color = const Color(0xFF000000),
+        _opacity = 1.0,
+        size = 24.0;
 
   /// Creates a copy of this icon theme but with the given fields replaced with
   /// the new values.
-  IconThemeData copyWith({ Color color, double opacity, double size }) {
+  IconThemeData copyWith({Color color, double opacity, double size}) {
     return new IconThemeData(
-      color: color ?? this.color,
-      opacity: opacity ?? this.opacity,
-      size: size ?? this.size
-    );
+        color: color ?? this.color, opacity: opacity ?? this.opacity, size: size ?? this.size);
   }
 
   /// Returns a new icon theme that matches this icon theme but with some values
@@ -44,11 +43,7 @@
   IconThemeData merge(IconThemeData other) {
     if (other == null)
       return this;
-    return copyWith(
-      color: other.color,
-      opacity: other.opacity,
-      size: other.size
-    );
+    return copyWith(color: other.color, opacity: other.opacity, size: other.size);
   }
 
   /// Whether all the properties of this object are non-null.
@@ -100,16 +95,13 @@
   int get hashCode => hashValues(color, opacity, size);
 
   @override
-  String toString() {
-    final List<String> result = <String>[];
-    if (color != null)
-      result.add('color: $color');
-    if (_opacity != null)
-      result.add('opacity: $_opacity');
-    if (size != null)
-      result.add('size: $size');
-    if (result.isEmpty)
-      return '<no theme>';
-    return result.join(', ');
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    if (color == null && _opacity == null && size == null) {
+      return;
+    }
+    description.add(new DiagnosticsProperty<Color>('color', color));
+    description.add(new DoubleProperty('opacity', _opacity));
+    description.add(new DoubleProperty('size', size));
   }
 }
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 6d2fb5b..d9438e8 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -150,8 +150,7 @@
     expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
   });
 
-  testWidgets('Slider can be given zero values',
-      (WidgetTester tester) async {
+  testWidgets('Slider can be given zero values', (WidgetTester tester) async {
     final List<double> log = <double>[];
     await tester.pumpWidget(new Directionality(
       textDirection: TextDirection.ltr,
@@ -160,7 +159,9 @@
           value: 0.0,
           min: 0.0,
           max: 1.0,
-          onChanged: (double newValue) { log.add(newValue); },
+          onChanged: (double newValue) {
+            log.add(newValue);
+          },
         ),
       ),
     ));
@@ -176,7 +177,9 @@
           value: 0.0,
           min: 0.0,
           max: 0.0,
-          onChanged: (double newValue) { log.add(newValue); },
+          onChanged: (double newValue) {
+            log.add(newValue);
+          },
         ),
       ),
     ));
@@ -186,11 +189,27 @@
     log.clear();
   });
 
-  testWidgets('Slider has a customizable active color',
+  testWidgets('Slider uses the right theme colors for the right components',
       (WidgetTester tester) async {
-    const Color customColor = const Color(0xFF4CD964);
-    final ThemeData theme = new ThemeData(platform: TargetPlatform.android);
-    Widget buildApp(Color activeColor) {
+    const Color customColor1 = const Color(0xcafefeed);
+    const Color customColor2 = const Color(0xdeadbeef);
+    final ThemeData theme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.blue,
+    );
+    final SliderThemeData sliderTheme = theme.sliderTheme;
+    double value = 0.45;
+    Widget buildApp({
+      Color activeColor,
+      Color inactiveColor,
+      int divisions,
+      bool enabled: true,
+    }) {
+      final ValueChanged<double> onChanged = !enabled
+          ? null
+          : (double d) {
+              value = d;
+            };
       return new Directionality(
         textDirection: TextDirection.ltr,
         child: new Material(
@@ -198,47 +217,12 @@
             child: new Theme(
               data: theme,
               child: new Slider(
-                value: 0.5,
+                value: value,
+                label: '$value',
+                divisions: divisions,
                 activeColor: activeColor,
-                onChanged: (double newValue) {},
-              ),
-            ),
-          ),
-        ),
-      );
-    }
-
-    await tester.pumpWidget(buildApp(null));
-
-    final RenderBox sliderBox =
-        tester.firstRenderObject<RenderBox>(find.byType(Slider));
-
-    expect(sliderBox, paints..rect(color: theme.accentColor)..rect(color: theme.unselectedWidgetColor));
-    expect(sliderBox, paints..circle(color: theme.accentColor));
-    expect(sliderBox, isNot(paints..circle(color: customColor)));
-    expect(sliderBox, isNot(paints..circle(color: theme.unselectedWidgetColor)));
-    await tester.pumpWidget(buildApp(customColor));
-    expect(sliderBox, paints..rect(color: customColor)..rect(color: theme.unselectedWidgetColor));
-    expect(sliderBox, paints..circle(color: customColor));
-    expect(sliderBox, isNot(paints..circle(color: theme.accentColor)));
-    expect(sliderBox, isNot(paints..circle(color: theme.unselectedWidgetColor)));
-  });
-
-  testWidgets('Slider has a customizable inactive color',
-      (WidgetTester tester) async {
-    const Color customColor = const Color(0xFF4CD964);
-    final ThemeData theme = new ThemeData(platform: TargetPlatform.android);
-    Widget buildApp(Color inactiveColor) {
-      return new Directionality(
-      textDirection: TextDirection.ltr,
-        child: new Material(
-          child: new Center(
-            child: new Theme(
-              data: theme,
-              child: new Slider(
-                value: 0.5,
                 inactiveColor: inactiveColor,
-                onChanged: (double newValue) {},
+                onChanged: onChanged,
               ),
             ),
           ),
@@ -246,78 +230,168 @@
       );
     }
 
-    await tester.pumpWidget(buildApp(null));
+    await tester.pumpWidget(buildApp());
 
-    final RenderBox sliderBox =
-        tester.firstRenderObject<RenderBox>(find.byType(Slider));
+    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
 
-    expect(sliderBox, paints..rect(color: theme.accentColor)..rect(color: theme.unselectedWidgetColor));
-    expect(sliderBox, paints..circle(color: theme.accentColor));
-    await tester.pumpWidget(buildApp(customColor));
-    expect(sliderBox, paints..rect(color: theme.accentColor)..rect(color: customColor));
-    expect(sliderBox, paints..circle(color: theme.accentColor));
+    // Check default theme for enabled widget.
+    expect(
+        sliderBox,
+        paints
+          ..rect(color: sliderTheme.activeRailColor)
+          ..rect(color: sliderTheme.inactiveRailColor));
+    expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
+
+    // Test setting only the activeColor.
+    await tester.pumpWidget(buildApp(activeColor: customColor1));
+    expect(
+        sliderBox, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveRailColor));
+    expect(sliderBox, paints..circle(color: customColor1));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
+
+    // Test setting only the inactiveColor.
+    await tester.pumpWidget(buildApp(inactiveColor: customColor1));
+    expect(sliderBox, paints..rect(color: sliderTheme.activeRailColor)..rect(color: customColor1));
+    expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
+
+    // Test setting both activeColor and inactiveColor.
+    await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2));
+    expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2));
+    expect(sliderBox, paints..circle(color: customColor1));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
+
+    // Test colors for discrete slider.
+    await tester.pumpWidget(buildApp(divisions: 3));
+    expect(
+        sliderBox,
+        paints
+          ..rect(color: sliderTheme.activeRailColor)
+          ..rect(color: sliderTheme.inactiveRailColor));
+    expect(
+        sliderBox,
+        paints
+          ..circle(color: sliderTheme.activeTickMarkColor)
+          ..circle(color: sliderTheme.activeTickMarkColor)
+          ..circle(color: sliderTheme.inactiveTickMarkColor)
+          ..circle(color: sliderTheme.inactiveTickMarkColor)
+          ..circle(color: sliderTheme.thumbColor));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
+
+    // Test colors for discrete slider with inactiveColor and activeColor set.
+    await tester
+        .pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, divisions: 3));
+    expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2));
+    expect(
+        sliderBox,
+        paints
+          ..circle(color: customColor2)
+          ..circle(color: customColor2)
+          ..circle(color: customColor1)
+          ..circle(color: customColor1)
+          ..circle(color: customColor1));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveRailColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
+
+    // Test default theme for disabled widget.
+    await tester.pumpWidget(buildApp(enabled: false));
+    await tester.pump(const Duration(seconds: 1)); // wait for disable animation to finish.
+    expect(
+        sliderBox,
+        paints
+          ..rect(color: sliderTheme.disabledActiveRailColor)
+          ..rect(color: sliderTheme.disabledInactiveRailColor));
+    expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveRailColor)));
+
+    // Test setting the activeColor and inactiveColor for disabled widget.
+    await tester.pumpWidget(
+        buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
+    expect(
+        sliderBox,
+        paints
+          ..rect(color: sliderTheme.disabledActiveRailColor)
+          ..rect(color: sliderTheme.disabledInactiveRailColor));
+    expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor));
+    expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeRailColor)));
+    expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveRailColor)));
+
+    // Test that the default value indicator has the right colors.
+    await tester.pumpWidget(buildApp(divisions: 3));
+    Offset center = tester.getCenter(find.byType(Slider));
+    TestGesture gesture = await tester.startGesture(center);
+    await tester.pump();
+    await tester
+        .pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish.
+    expect(value, equals(2.0 / 3.0));
+    expect(
+      sliderBox,
+      paints
+        ..rect(color: sliderTheme.activeRailColor)
+        ..rect(color: sliderTheme.inactiveRailColor)
+        ..circle(color: sliderTheme.overlayColor)
+        ..circle(color: sliderTheme.activeTickMarkColor)
+        ..circle(color: sliderTheme.activeTickMarkColor)
+        ..circle(color: sliderTheme.inactiveTickMarkColor)
+        ..circle(color: sliderTheme.inactiveTickMarkColor)
+        ..path(color: sliderTheme.valueIndicatorColor)
+        ..circle(color: sliderTheme.thumbColor),
+    );
+    await gesture.up();
+    await tester.pump();
+    await tester
+        .pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish.
+
+    // Testing the custom colors are used for the indicator.
+    await tester.pumpWidget(buildApp(
+      divisions: 3,
+      activeColor: customColor1,
+      inactiveColor: customColor2,
+    ));
+    center = tester.getCenter(find.byType(Slider));
+    gesture = await tester.startGesture(center);
+    await tester.pump();
+    await tester
+        .pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish.
+    expect(value, equals(2.0 / 3.0));
+    expect(
+      sliderBox,
+      paints
+        ..rect(color: customColor1)
+        ..rect(color: customColor2)
+        ..circle(color: customColor1.withAlpha(0x29))
+        ..circle(color: customColor2)
+        ..circle(color: customColor2)
+        ..circle(color: customColor1)
+        ..path(color: customColor1)
+        ..circle(color: customColor1),
+    );
+    await gesture.up();
   });
 
-  testWidgets('Slider can draw an open thumb at min (LTR)',
-      (WidgetTester tester) async {
-    Widget buildApp(bool thumbOpenAtMin) {
-      return new Directionality(
-        textDirection: TextDirection.ltr,
-        child: new Material(
-          child: new Center(
-            child: new Slider(
-              value: 0.0,
-              thumbOpenAtMin: thumbOpenAtMin,
-              onChanged: (double newValue) {},
-            ),
-          ),
-        ),
-      );
-    }
-
-    await tester.pumpWidget(buildApp(false));
-
-    final RenderBox sliderBox =
-        tester.firstRenderObject<RenderBox>(find.byType(Slider));
-
-    expect(sliderBox, paints..circle(style: PaintingStyle.fill));
-    expect(sliderBox, isNot(paints..circle()..circle()));
-    await tester.pumpWidget(buildApp(true));
-    expect(sliderBox, paints..circle(style: PaintingStyle.stroke));
-    expect(sliderBox, isNot(paints..circle()..circle()));
-  });
-
-  testWidgets('Slider can draw an open thumb at min (RTL)',
-      (WidgetTester tester) async {
-    Widget buildApp(bool thumbOpenAtMin) {
-      return new Directionality(
-        textDirection: TextDirection.rtl,
-        child: new Material(
-          child: new Center(
-            child: new Slider(
-              value: 0.0,
-              thumbOpenAtMin: thumbOpenAtMin,
-              onChanged: (double newValue) {},
-            ),
-          ),
-        ),
-      );
-    }
-
-    await tester.pumpWidget(buildApp(false));
-
-    final RenderBox sliderBox =
-        tester.firstRenderObject<RenderBox>(find.byType(Slider));
-
-    expect(sliderBox, paints..circle(style: PaintingStyle.fill));
-    expect(sliderBox, isNot(paints..circle()..circle()));
-    await tester.pumpWidget(buildApp(true));
-    expect(sliderBox, paints..circle(style: PaintingStyle.stroke));
-    expect(sliderBox, isNot(paints..circle()..circle()));
-  });
-
-  testWidgets('Slider can tap in vertical scroller',
-      (WidgetTester tester) async {
+  testWidgets('Slider can tap in vertical scroller', (WidgetTester tester) async {
     double value = 0.0;
     await tester.pumpWidget(new Directionality(
       textDirection: TextDirection.ltr,
@@ -425,7 +499,8 @@
         ),
       ),
     ));
-    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 600.0));
+    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size,
+        const Size(144.0 + 2.0 * 16.0, 600.0));
 
     await tester.pumpWidget(const Directionality(
       textDirection: TextDirection.ltr,
@@ -442,14 +517,18 @@
         ),
       ),
     ));
-    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size, const Size(144.0 + 2.0 * 16.0, 32.0));
+    expect(tester.renderObject<RenderBox>(find.byType(Slider)).size,
+        const Size(144.0 + 2.0 * 16.0, 32.0));
   });
 
-  testWidgets('discrete Slider respects textScaleFactor', (WidgetTester tester) async {
+  testWidgets('Slider respects textScaleFactor', (WidgetTester tester) async {
     final Key sliderKey = new UniqueKey();
     double value = 0.0;
 
-    Widget buildSlider({ double textScaleFactor }) {
+    Widget buildSlider(
+        {double textScaleFactor,
+        bool isDiscrete: true,
+        ShowValueIndicator show: ShowValueIndicator.onlyForDiscrete}) {
       return new Directionality(
         textDirection: TextDirection.ltr,
         child: new StatefulBuilder(
@@ -457,22 +536,27 @@
             return new MediaQuery(
               data: new MediaQueryData(textScaleFactor: textScaleFactor),
               child: new Material(
-                child: new Center(
-                  child: new OverflowBox(
-                    maxWidth: double.INFINITY,
-                    maxHeight: double.INFINITY,
-                    child: new Slider(
-                      key: sliderKey,
-                      min: 0.0,
-                      max: 100.0,
-                      divisions: 10,
-                      label: '${value.round()}',
-                      value: value,
-                      onChanged: (double newValue) {
-                        setState(() {
-                          value = newValue;
-                        });
-                      },
+                child: new Theme(
+                  data: Theme.of(context).copyWith(
+                      sliderTheme:
+                          Theme.of(context).sliderTheme.copyWith(showValueIndicator: show)),
+                  child: new Center(
+                    child: new OverflowBox(
+                      maxWidth: double.INFINITY,
+                      maxHeight: double.INFINITY,
+                      child: new Slider(
+                        key: sliderKey,
+                        min: 0.0,
+                        max: 100.0,
+                        divisions: isDiscrete ? 10 : null,
+                        label: '${value.round()}',
+                        value: value,
+                        onChanged: (double newValue) {
+                          setState(() {
+                            value = newValue;
+                          });
+                        },
+                      ),
                     ),
                   ),
                 ),
@@ -486,12 +570,10 @@
     await tester.pumpWidget(buildSlider(textScaleFactor: 1.0));
     Offset center = tester.getCenter(find.byType(Slider));
     TestGesture gesture = await tester.startGesture(center);
-    await gesture.moveBy(const Offset(10.0, 0.0));
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 500));
 
-    expect(
-      tester.renderObject(find.byType(Slider)),
-      paints..circle(radius: 6.0, x: 16.0, y: 44.0)
-    );
+    expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 1.0, y: 1.0));
 
     await gesture.up();
     await tester.pump(const Duration(seconds: 1));
@@ -499,12 +581,41 @@
     await tester.pumpWidget(buildSlider(textScaleFactor: 2.0));
     center = tester.getCenter(find.byType(Slider));
     gesture = await tester.startGesture(center);
-    await gesture.moveBy(const Offset(10.0, 0.0));
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 500));
 
-    expect(
-      tester.renderObject(find.byType(Slider)),
-      paints..circle(radius: 12.0, x: 16.0, y: 44.0)
-    );
+    expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 2.0, y: 2.0));
+
+    await gesture.up();
+    await tester.pump(const Duration(seconds: 1));
+
+    // Check continuous
+    await tester.pumpWidget(buildSlider(
+      textScaleFactor: 1.0,
+      isDiscrete: false,
+      show: ShowValueIndicator.onlyForContinuous,
+    ));
+    center = tester.getCenter(find.byType(Slider));
+    gesture = await tester.startGesture(center);
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 500));
+
+    expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 1.0, y: 1.0));
+
+    await gesture.up();
+    await tester.pump(const Duration(seconds: 1));
+
+    await tester.pumpWidget(buildSlider(
+      textScaleFactor: 2.0,
+      isDiscrete: false,
+      show: ShowValueIndicator.onlyForContinuous,
+    ));
+    center = tester.getCenter(find.byType(Slider));
+    gesture = await tester.startGesture(center);
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 500));
+
+    expect(tester.renderObject(find.byType(Slider)), paints..scale(x: 2.0, y: 2.0));
 
     await gesture.up();
     await tester.pump(const Duration(seconds: 1));
@@ -523,18 +634,18 @@
       ),
     ));
 
-    expect(semantics, hasSemantics(
-      new TestSemantics.root(
-          children: <TestSemantics>[
+    expect(
+        semantics,
+        hasSemantics(
+          new TestSemantics.root(children: <TestSemantics>[
             new TestSemantics.rootChild(
               id: 1,
               actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
             ),
-          ]
-      ),
-      ignoreRect: true,
-      ignoreTransform: true,
-    ));
+          ]),
+          ignoreRect: true,
+          ignoreTransform: true,
+        ));
 
     // Disable slider
     await tester.pumpWidget(const Directionality(
@@ -547,12 +658,92 @@
       ),
     ));
 
-    expect(semantics, hasSemantics(
-      new TestSemantics.root(),
-      ignoreRect: true,
-      ignoreTransform: true,
-    ));
+    expect(
+        semantics,
+        hasSemantics(
+          new TestSemantics.root(),
+          ignoreRect: true,
+          ignoreTransform: true,
+        ));
 
     semantics.dispose();
   });
+
+  testWidgets('Value indicator appears when it should', (WidgetTester tester) async {
+    final ThemeData baseTheme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.blue,
+    );
+    SliderThemeData theme = baseTheme.sliderTheme;
+    double value = 0.45;
+    Widget buildApp({SliderThemeData sliderTheme, int divisions, bool enabled: true}) {
+      final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
+      return new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Material(
+          child: new Center(
+            child: new Theme(
+              data: baseTheme,
+              child: new SliderTheme(
+                data: sliderTheme,
+                child: new Slider(
+                  value: value,
+                  label: '$value',
+                  divisions: divisions,
+                  onChanged: onChanged,
+                ),
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    Future<Null> expectValueIndicator(
+        {bool isVisible, SliderThemeData theme, int divisions, bool enabled: true}) async {
+      // discrete enabled widget.
+      await tester.pumpWidget(buildApp(sliderTheme: theme, divisions: divisions, enabled: enabled));
+      final Offset center = tester.getCenter(find.byType(Slider));
+      final TestGesture gesture = await tester.startGesture(center);
+      await tester.pump();
+      await tester
+          .pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish.
+
+      final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
+      expect(
+        sliderBox,
+        isVisible
+            ? (paints..path(color: theme.valueIndicatorColor))
+            : isNot(paints..path(color: theme.valueIndicatorColor)),
+      );
+      await gesture.up();
+    }
+
+    // Default (showValueIndicator set to onlyForDiscrete).
+    await expectValueIndicator(isVisible: true, theme: theme, divisions: 3, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
+    await expectValueIndicator(isVisible: false, theme: theme, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);
+
+    // With showValueIndicator set to onlyForContinuous.
+    theme = theme.copyWith(showValueIndicator: ShowValueIndicator.onlyForContinuous);
+    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
+    await expectValueIndicator(isVisible: true, theme: theme, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);
+
+    // discrete enabled widget with showValueIndicator set to always.
+    theme = theme.copyWith(showValueIndicator: ShowValueIndicator.always);
+    await expectValueIndicator(isVisible: true, theme: theme, divisions: 3, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
+    await expectValueIndicator(isVisible: true, theme: theme, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);
+
+    // discrete enabled widget with showValueIndicator set to never.
+    theme = theme.copyWith(showValueIndicator: ShowValueIndicator.never);
+    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, divisions: 3, enabled: false);
+    await expectValueIndicator(isVisible: false, theme: theme, enabled: true);
+    await expectValueIndicator(isVisible: false, theme: theme, enabled: false);
+  });
 }
diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart
new file mode 100644
index 0000000..0c7c1b7
--- /dev/null
+++ b/packages/flutter/test/material/slider_theme_test.dart
@@ -0,0 +1,294 @@
+// Copyright 2018 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/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/painting.dart';
+
+import '../rendering/mock_canvas.dart';
+
+void main() {
+  testWidgets('Slider theme is built by ThemeData', (WidgetTester tester) async {
+    final ThemeData theme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.red,
+    );
+    final SliderThemeData sliderTheme = theme.sliderTheme;
+
+    expect(sliderTheme.activeRailColor.value, equals(Colors.red.value));
+    expect(sliderTheme.inactiveRailColor.value, equals(Colors.red.withAlpha(0x3d).value));
+  });
+
+  testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
+    final ThemeData theme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.red,
+    );
+    final SliderThemeData sliderTheme = theme.sliderTheme;
+
+    Widget buildSlider(SliderThemeData data) {
+      return new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Material(
+          child: new Center(
+            child: new Theme(
+              data: theme,
+              child: const Slider(
+                value: 0.5,
+                label: '0.5',
+                onChanged: null,
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(buildSlider(sliderTheme));
+
+    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
+
+    expect(
+        sliderBox,
+        paints
+          ..rect(color: sliderTheme.disabledActiveRailColor)
+          ..rect(color: sliderTheme.disabledInactiveRailColor));
+  });
+
+  testWidgets('Slider overrides ThemeData theme if SliderTheme present',
+      (WidgetTester tester) async {
+    final ThemeData theme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.red,
+    );
+    final SliderThemeData sliderTheme = theme.sliderTheme;
+    final SliderThemeData customTheme = sliderTheme.copyWith(
+      activeRailColor: Colors.purple,
+      inactiveRailColor: Colors.purple.withAlpha(0x3d),
+    );
+
+    Widget buildSlider(SliderThemeData data) {
+      return new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Material(
+          child: new Center(
+            child: new Theme(
+              data: theme,
+              child: new SliderTheme(
+                data: customTheme,
+                child: const Slider(
+                  value: 0.5,
+                  label: '0.5',
+                  onChanged: null,
+                ),
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(buildSlider(sliderTheme));
+
+    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
+
+    expect(
+        sliderBox,
+        paints
+          ..rect(color: customTheme.disabledActiveRailColor)
+          ..rect(color: customTheme.disabledInactiveRailColor));
+  });
+
+  testWidgets('SliderThemeData generates correct opacities for materialDefaults',
+      (WidgetTester tester) async {
+    const Color customColor1 = const Color(0xcafefeed);
+    const Color customColor2 = const Color(0xdeadbeef);
+    const Color customColor3 = const Color(0xdecaface);
+
+    final SliderThemeData sliderTheme = new SliderThemeData.materialDefaults(
+      primaryColor: customColor1,
+      primaryColorDark: customColor2,
+      primaryColorLight: customColor3,
+    );
+
+    expect(sliderTheme.activeRailColor, equals(customColor1.withAlpha(0xff)));
+    expect(sliderTheme.inactiveRailColor, equals(customColor1.withAlpha(0x3d)));
+    expect(sliderTheme.disabledActiveRailColor, equals(customColor2.withAlpha(0x52)));
+    expect(sliderTheme.disabledInactiveRailColor, equals(customColor2.withAlpha(0x1f)));
+    expect(sliderTheme.activeTickMarkColor, equals(customColor3.withAlpha(0x8a)));
+    expect(sliderTheme.inactiveTickMarkColor, equals(customColor1.withAlpha(0x8a)));
+    expect(sliderTheme.disabledActiveTickMarkColor, equals(customColor3.withAlpha(0x1f)));
+    expect(sliderTheme.disabledInactiveTickMarkColor, equals(customColor2.withAlpha(0x1f)));
+    expect(sliderTheme.thumbColor, equals(customColor1.withAlpha(0xff)));
+    expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52)));
+    expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x29)));
+    expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
+    expect(sliderTheme.thumbShape, equals(const isInstanceOf<RoundSliderThumbShape>()));
+    expect(sliderTheme.valueIndicatorShape,
+        equals(const isInstanceOf<PaddleSliderValueIndicatorShape>()));
+    expect(sliderTheme.showValueIndicator, equals(ShowValueIndicator.onlyForDiscrete));
+  });
+
+  testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async {
+    final SliderThemeData sliderThemeBlack = new SliderThemeData.materialDefaults(
+      primaryColor: Colors.black,
+      primaryColorDark: Colors.black,
+      primaryColorLight: Colors.black,
+    );
+    final SliderThemeData sliderThemeWhite = new SliderThemeData.materialDefaults(
+      primaryColor: Colors.white,
+      primaryColorDark: Colors.white,
+      primaryColorLight: Colors.white,
+    );
+    final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
+    const Color middleGrey = const Color(0xff7f7f7f);
+    expect(lerp.activeRailColor, equals(middleGrey.withAlpha(0xff)));
+    expect(lerp.inactiveRailColor, equals(middleGrey.withAlpha(0x3d)));
+    expect(lerp.disabledActiveRailColor, equals(middleGrey.withAlpha(0x52)));
+    expect(lerp.disabledInactiveRailColor, equals(middleGrey.withAlpha(0x1f)));
+    expect(lerp.activeTickMarkColor, equals(middleGrey.withAlpha(0x8a)));
+    expect(lerp.inactiveTickMarkColor, equals(middleGrey.withAlpha(0x8a)));
+    expect(lerp.disabledActiveTickMarkColor, equals(middleGrey.withAlpha(0x1f)));
+    expect(lerp.disabledInactiveTickMarkColor, equals(middleGrey.withAlpha(0x1f)));
+    expect(lerp.thumbColor, equals(middleGrey.withAlpha(0xff)));
+    expect(lerp.disabledThumbColor, equals(middleGrey.withAlpha(0x52)));
+    expect(lerp.overlayColor, equals(middleGrey.withAlpha(0x29)));
+    expect(lerp.valueIndicatorColor, equals(middleGrey.withAlpha(0xff)));
+  });
+
+  testWidgets('Default slider thumb shape draws correctly', (WidgetTester tester) async {
+    final ThemeData theme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.blue,
+    );
+    final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
+    double value = 0.45;
+    Widget buildApp({
+      int divisions,
+      bool enabled: true,
+    }) {
+      final ValueChanged<double> onChanged = enabled ? (double d) => value = d : null;
+      return new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Material(
+          child: new Center(
+            child: new SliderTheme(
+              data: sliderTheme,
+              child: new Slider(
+                value: value,
+                label: '$value',
+                divisions: divisions,
+                onChanged: onChanged,
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(buildApp());
+
+    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
+
+    expect(sliderBox, paints..circle(color: sliderTheme.thumbColor, radius: 6.0));
+
+    await tester.pumpWidget(buildApp(enabled: false));
+    await tester.pump(const Duration(milliseconds: 500)); // wait for disable animation
+    expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor, radius: 4.0));
+
+    await tester.pumpWidget(buildApp(divisions: 3));
+    await tester.pump(const Duration(milliseconds: 500)); // wait for disable animation
+    expect(
+        sliderBox,
+        paints
+          ..circle(color: sliderTheme.activeTickMarkColor)
+          ..circle(color: sliderTheme.activeTickMarkColor)
+          ..circle(color: sliderTheme.inactiveTickMarkColor)
+          ..circle(color: sliderTheme.inactiveTickMarkColor)
+          ..circle(color: sliderTheme.thumbColor, radius: 6.0));
+
+    await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
+    await tester.pump(const Duration(milliseconds: 500)); // wait for disable animation
+    expect(
+        sliderBox,
+        paints
+          ..circle(color: sliderTheme.disabledActiveTickMarkColor)
+          ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
+          ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
+          ..circle(color: sliderTheme.disabledThumbColor, radius: 4.0));
+  });
+
+  testWidgets('Default slider value indicator shape draws correctly', (WidgetTester tester) async {
+    final ThemeData theme = new ThemeData(
+      platform: TargetPlatform.android,
+      primarySwatch: Colors.blue,
+    );
+    final SliderThemeData sliderTheme = theme.sliderTheme
+        .copyWith(thumbColor: Colors.red.shade500, showValueIndicator: ShowValueIndicator.always);
+    Widget buildApp(String value) {
+      return new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Material(
+          child: new Center(
+            child: new SliderTheme(
+              data: sliderTheme,
+              child: new Slider(
+                value: 0.5,
+                label: '$value',
+                divisions: 3,
+                onChanged: (double d) {},
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(buildApp('1'));
+
+    final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
+
+    Offset center = tester.getCenter(find.byType(Slider));
+    TestGesture gesture = await tester.startGesture(center);
+    await tester.pump();
+    // Wait for value indicator animation to finish.
+    await tester.pump(const Duration(milliseconds: 500));
+    expect(
+        sliderBox,
+        paints
+          ..path(
+            color: sliderTheme.valueIndicatorColor,
+            includes: <Offset>[
+              const Offset(0.0, -40.0),
+              const Offset(15.9, -40.0),
+              const Offset(-15.9, -40.0),
+            ],
+            excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
+          ));
+
+    await gesture.up();
+
+    // Test that it expands with a larger label.
+    await tester.pumpWidget(buildApp('1000'));
+    center = tester.getCenter(find.byType(Slider));
+    gesture = await tester.startGesture(center);
+    await tester.pump();
+    // Wait for value indicator animation to finish.
+    await tester.pump(const Duration(milliseconds: 500));
+    expect(
+        sliderBox,
+        paints
+          ..path(
+            color: sliderTheme.valueIndicatorColor,
+            includes: <Offset>[
+              const Offset(0.0, -40.0),
+              const Offset(35.9, -40.0),
+              const Offset(-35.9, -40.0),
+            ],
+            excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
+          ));
+    await gesture.up();
+  });
+}