Clean up baseline logic in material library. (#17922)

diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart
index 721fbb8..33f9e2b 100644
--- a/packages/flutter/lib/src/material/chip.dart
+++ b/packages/flutter/lib/src/material/chip.dart
@@ -1855,7 +1855,7 @@
   @override
   double computeDistanceToActualBaseline(TextBaseline baseline) {
     // The baseline of this widget is the baseline of the label.
-    return label.computeDistanceToActualBaseline(baseline);
+    return label.getDistanceToActualBaseline(baseline);
   }
 
   Size _layoutLabel(double iconSizes, Size size) {
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart
index 672011a..3caace3 100644
--- a/packages/flutter/lib/src/material/input_decorator.dart
+++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -537,10 +537,15 @@
 // The workhorse: layout and paint a _Decorator widget's _Decoration.
 class _RenderDecoration extends RenderBox {
   _RenderDecoration({
-    _Decoration decoration,
-    TextDirection textDirection,
-  }) : _decoration = decoration,
-       _textDirection = textDirection;
+    @required _Decoration decoration,
+    @required TextDirection textDirection,
+    @required TextBaseline textBaseline,
+  }) : assert(decoration != null),
+       assert(textDirection != null),
+       assert(textBaseline != null),
+       _decoration = decoration,
+       _textDirection = textDirection,
+       _textBaseline = textBaseline;
 
   final Map<_DecorationSlot, RenderBox> slotToChild = <_DecorationSlot, RenderBox>{};
   final Map<RenderBox, _DecorationSlot> childToSlot = <RenderBox, _DecorationSlot>{};
@@ -654,6 +659,7 @@
   _Decoration get decoration => _decoration;
   _Decoration _decoration;
   set decoration(_Decoration value) {
+    assert(value != null);
     if (_decoration == value)
       return;
     _decoration = value;
@@ -663,12 +669,23 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
+    assert(value != null);
     if (_textDirection == value)
       return;
     _textDirection = value;
     markNeedsLayout();
   }
 
+  TextBaseline get textBaseline => _textBaseline;
+  TextBaseline _textBaseline;
+  set textBaseline(TextBaseline value) {
+    assert(value != null);
+    if (_textBaseline == value)
+      return;
+    _textBaseline = value;
+    markNeedsLayout();
+  }
+
   @override
   void attach(PipelineOwner owner) {
     super.attach(owner);
@@ -748,7 +765,7 @@
       if (box == null)
         return;
       box.layout(boxConstraints, parentUsesSize: true);
-      final double baseline = box.getDistanceToBaseline(TextBaseline.alphabetic);
+      final double baseline = box.getDistanceToBaseline(textBaseline);
       assert(baseline != null && baseline >= 0.0);
       boxToBaseline[box] = baseline;
       aboveBaseline = math.max(baseline, aboveBaseline);
@@ -913,7 +930,15 @@
         width: overallWidth - _boxSize(icon).width,
       );
       container.layout(containerConstraints, parentUsesSize: true);
-      final double x = textDirection == TextDirection.rtl ? 0.0 : _boxSize(icon).width;
+      double x;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          x = 0.0;
+          break;
+        case TextDirection.ltr:
+          x = _boxSize(icon).width;
+          break;
+       }
       _boxParentData(container).offset = new Offset(x, 0.0);
     }
 
@@ -938,7 +963,15 @@
       : layout.outlineBaseline;
 
     if (icon != null) {
-      final double x = textDirection == TextDirection.rtl ? overallWidth - icon.size.width : 0.0;
+      double x;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          x = overallWidth - icon.size.width;
+          break;
+        case TextDirection.ltr:
+          x = 0.0;
+          break;
+       }
       centerLayout(icon, x);
     }
 
@@ -1004,9 +1037,14 @@
     }
 
     if (label != null) {
-      decoration.borderGap.start = textDirection == TextDirection.rtl
-        ? _boxParentData(label).offset.dx + label.size.width
-        : _boxParentData(label).offset.dx;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          decoration.borderGap.start = _boxParentData(label).offset.dx + label.size.width;
+          break;
+        case TextDirection.ltr:
+          decoration.borderGap.start = _boxParentData(label).offset.dx;
+          break;
+      }
       decoration.borderGap.extent = label.size.width * 0.75;
     } else {
       decoration.borderGap.start = null;
@@ -1039,9 +1077,15 @@
       final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
       final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
       final double scale = lerpDouble(1.0, 0.75, t);
-      final double dx = textDirection == TextDirection.rtl
-        ? labelOffset.dx + label.size.width * (1.0 - scale) // origin is on the right
-        : labelOffset.dx; // origin on the left
+      double dx;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          dx = labelOffset.dx + label.size.width * (1.0 - scale); // origin is on the right
+          break;
+        case TextDirection.ltr:
+          dx = labelOffset.dx; // origin on the left
+          break;
+      }
       final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t);
       _labelTransform = new Matrix4.identity()
         ..translate(dx, labelOffset.dy + dy)
@@ -1237,10 +1281,17 @@
 class _Decorator extends RenderObjectWidget {
   const _Decorator({
     Key key,
-    this.decoration,
-  }) : super(key: key);
+    @required this.decoration,
+    @required this.textDirection,
+    @required this.textBaseline,
+  }) : assert(decoration != null),
+       assert(textDirection != null),
+       assert(textBaseline != null),
+       super(key: key);
 
   final _Decoration decoration;
+  final TextDirection textDirection;
+  final TextBaseline textBaseline;
 
   @override
   _RenderDecorationElement createElement() => new _RenderDecorationElement(this);
@@ -1249,7 +1300,8 @@
   _RenderDecoration createRenderObject(BuildContext context) {
     return new _RenderDecoration(
       decoration: decoration,
-      textDirection: Directionality.of(context),
+      textDirection: textDirection,
+      textBaseline: textBaseline,
     );
   }
 
@@ -1257,7 +1309,8 @@
   void updateRenderObject(BuildContext context, _RenderDecoration renderObject) {
     renderObject
      ..decoration = decoration
-     ..textDirection = Directionality.of(context);
+     ..textDirection = textDirection
+     ..textBaseline = textBaseline;
   }
 }
 
@@ -1310,6 +1363,9 @@
   ///
   /// If null, `baseStyle` defaults to the `subhead` style from the
   /// current [Theme], see [ThemeData.textTheme].
+  ///
+  /// The [TextStyle.textBaseline] of the [baseStyle] is used to determine
+  /// the baseline used for text alignment.
   final TextStyle baseStyle;
 
   /// How the text in the decoration should be aligned horizontally.
@@ -1539,6 +1595,7 @@
   Widget build(BuildContext context) {
     final ThemeData themeData = Theme.of(context);
     final TextStyle inlineStyle = _getInlineStyle(themeData);
+    final TextBaseline textBaseline = inlineStyle.textBaseline;
 
     final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle);
     final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity(
@@ -1713,6 +1770,8 @@
         counter: counter,
         container: container,
       ),
+      textDirection: textDirection,
+      textBaseline: textBaseline,
     );
   }
 }
diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart
index f87d0ed..82ef80c 100644
--- a/packages/flutter/lib/src/material/list_tile.dart
+++ b/packages/flutter/lib/src/material/list_tile.dart
@@ -420,16 +420,19 @@
       );
     }
 
+    final TextStyle titleStyle = _titleTextStyle(theme, tileTheme);
     final Widget titleText = new AnimatedDefaultTextStyle(
-      style: _titleTextStyle(theme, tileTheme),
+      style: titleStyle,
       duration: kThemeChangeDuration,
       child: title ?? const SizedBox()
     );
 
     Widget subtitleText;
+    TextStyle subtitleStyle;
     if (subtitle != null) {
+      subtitleStyle = _subtitleTextStyle(theme, tileTheme);
       subtitleText = new AnimatedDefaultTextStyle(
-        style: _subtitleTextStyle(theme, tileTheme),
+        style: subtitleStyle,
         duration: kThemeChangeDuration,
         child: subtitle,
       );
@@ -466,6 +469,9 @@
             trailing: trailingIcon,
             isDense: _isDenseLayout(tileTheme),
             isThreeLine: isThreeLine,
+            textDirection: textDirection,
+            titleBaselineType: titleStyle.textBaseline,
+            subtitleBaselineType: subtitleStyle?.textBaseline,
           ),
         ),
       ),
@@ -473,45 +479,6 @@
   }
 }
 
-class _ListTile extends RenderObjectWidget {
-  const _ListTile({
-    Key key,
-    this.leading,
-    this.title,
-    this.subtitle,
-    this.trailing,
-    this.isThreeLine,
-    this.isDense,
-  }) : super(key: key);
-
-  final Widget leading;
-  final Widget title;
-  final Widget subtitle;
-  final Widget trailing;
-  final bool isThreeLine;
-  final bool isDense;
-
-  @override
-  _RenderListTileElement createElement() => new _RenderListTileElement(this);
-
-  @override
-  _RenderListTile createRenderObject(BuildContext context) {
-    return new _RenderListTile(
-      isThreeLine: isThreeLine,
-      isDense: isDense,
-      textDirection: Directionality.of(context),
-    );
-  }
-
-  @override
-  void updateRenderObject(BuildContext context, _RenderListTile renderObject) {
-    renderObject
-      ..isThreeLine = isThreeLine
-      ..isDense = isDense
-      ..textDirection = Directionality.of(context);
-  }
-}
-
 // Identifies the children of a _ListTileElement.
 enum _ListTileSlot {
   leading,
@@ -520,348 +487,61 @@
   trailing,
 }
 
-class _RenderListTile extends RenderBox {
-  _RenderListTile({
-    bool isDense,
-    bool isThreeLine,
-    TextDirection textDirection,
-  }) : _isDense = isDense,
-       _isThreeLine = isThreeLine,
-       _textDirection = textDirection;
+class _ListTile extends RenderObjectWidget {
+  const _ListTile({
+    Key key,
+    this.leading,
+    this.title,
+    this.subtitle,
+    this.trailing,
+    @required this.isThreeLine,
+    @required this.isDense,
+    @required this.textDirection,
+    @required this.titleBaselineType,
+    this.subtitleBaselineType,
+  }) : assert(isThreeLine != null),
+       assert(isDense != null),
+       assert(textDirection != null),
+       assert(titleBaselineType != null),
+       super(key: key);
 
-  static const double _minLeadingWidth = 40.0;
-  // The horizontal gap between the titles and the leading/trailing widgets
-  static const double _horizontalTitleGap = 16.0;
-  // The minimum padding on the top and bottom of the title and subtitle widgets.
-  static const double _minVerticalPadding = 4.0;
-
-  final Map<_ListTileSlot, RenderBox> slotToChild = <_ListTileSlot, RenderBox>{};
-  final Map<RenderBox, _ListTileSlot> childToSlot = <RenderBox, _ListTileSlot>{};
-
-  RenderBox _updateChild(RenderBox oldChild, RenderBox newChild, _ListTileSlot slot) {
-    if (oldChild != null) {
-      dropChild(oldChild);
-      childToSlot.remove(oldChild);
-      slotToChild.remove(slot);
-    }
-    if (newChild != null) {
-      childToSlot[newChild] = slot;
-      slotToChild[slot] = newChild;
-      adoptChild(newChild);
-    }
-    return newChild;
-  }
-
-  RenderBox _leading;
-  RenderBox get leading => _leading;
-  set leading(RenderBox value) {
-    _leading = _updateChild(_leading, value, _ListTileSlot.leading);
-  }
-
-  RenderBox _title;
-  RenderBox get title => _title;
-  set title(RenderBox value) {
-    _title = _updateChild(_title, value, _ListTileSlot.title);
-  }
-
-  RenderBox _subtitle;
-  RenderBox get subtitle => _subtitle;
-  set subtitle(RenderBox value) {
-    _subtitle = _updateChild(_subtitle, value, _ListTileSlot.subtitle);
-  }
-
-  RenderBox _trailing;
-  RenderBox get trailing => _trailing;
-  set trailing(RenderBox value) {
-    _trailing = _updateChild(_trailing, value, _ListTileSlot.trailing);
-  }
-
-  // The returned list is ordered for hit testing.
-  Iterable<RenderBox> get _children sync *{
-    if (leading != null)
-      yield leading;
-    if (title != null)
-      yield title;
-    if (subtitle != null)
-      yield subtitle;
-    if (trailing != null)
-      yield trailing;
-  }
-
-  bool get isDense => _isDense;
-  bool _isDense;
-  set isDense(bool value) {
-    if (_isDense == value)
-      return;
-    _isDense = value;
-    markNeedsLayout();
-  }
-
-  bool get isThreeLine => _isThreeLine;
-  bool _isThreeLine;
-  set isThreeLine(bool value) {
-    if (_isThreeLine == value)
-      return;
-    _isThreeLine = value;
-    markNeedsLayout();
-  }
-
-  TextDirection get textDirection => _textDirection;
-  TextDirection _textDirection;
-  set textDirection(TextDirection value) {
-    if (_textDirection == value)
-      return;
-    _textDirection = value;
-    markNeedsLayout();
-  }
+  final Widget leading;
+  final Widget title;
+  final Widget subtitle;
+  final Widget trailing;
+  final bool isThreeLine;
+  final bool isDense;
+  final TextDirection textDirection;
+  final TextBaseline titleBaselineType;
+  final TextBaseline subtitleBaselineType;
 
   @override
-  void attach(PipelineOwner owner) {
-    super.attach(owner);
-    for (RenderBox child in _children)
-      child.attach(owner);
-  }
+  _ListTileElement createElement() => new _ListTileElement(this);
 
   @override
-  void detach() {
-    super.detach();
-    for (RenderBox child in _children)
-      child.detach();
-  }
-
-  @override
-  void redepthChildren() {
-    _children.forEach(redepthChild);
-  }
-
-  @override
-  void visitChildren(RenderObjectVisitor visitor) {
-    _children.forEach(visitor);
-  }
-
-  @override
-  List<DiagnosticsNode> debugDescribeChildren() {
-    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
-    void add(RenderBox child, String name) {
-      if (child != null)
-        value.add(child.toDiagnosticsNode(name: name));
-    }
-    add(leading, 'leading');
-    add(title, 'title');
-    add(subtitle, 'subtitle');
-    add(trailing, 'trailing');
-    return value;
-  }
-
-  @override
-  bool get sizedByParent => false;
-
-  static double _minWidth(RenderBox box, double height) {
-    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
-  }
-
-  static double _maxWidth(RenderBox box, double height) {
-    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
-  }
-
-  @override
-  double computeMinIntrinsicWidth(double height) {
-    final double leadingWidth = leading != null
-      ? math.max(leading.getMinIntrinsicWidth(height), _minLeadingWidth) + _horizontalTitleGap
-      : 0.0;
-    return leadingWidth
-      + math.max(_minWidth(title, height), _minWidth(subtitle, height))
-      + _maxWidth(trailing, height);
-  }
-
-  @override
-  double computeMaxIntrinsicWidth(double height) {
-    final double leadingWidth = leading != null
-      ? math.max(leading.getMaxIntrinsicWidth(height), _minLeadingWidth) + _horizontalTitleGap
-      : 0.0;
-    return leadingWidth
-      + math.max(_maxWidth(title, height), _maxWidth(subtitle, height))
-      + _maxWidth(trailing, height);
-  }
-
-  double get _defaultTileHeight {
-    final bool hasSubtitle = subtitle != null;
-    final bool isTwoLine = !isThreeLine && hasSubtitle;
-    final bool isOneLine = !isThreeLine && !hasSubtitle;
-
-    if (isOneLine)
-      return isDense ? 48.0 : 56.0;
-    if (isTwoLine)
-      return isDense ? 64.0 : 72.0;
-    return isDense ? 76.0 : 88.0;
-  }
-
-  @override
-  double computeMinIntrinsicHeight(double width) {
-    return math.max(
-      _defaultTileHeight,
-      title.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0)
+  _RenderListTile createRenderObject(BuildContext context) {
+    return new _RenderListTile(
+      isThreeLine: isThreeLine,
+      isDense: isDense,
+      textDirection: textDirection,
+      titleBaselineType: titleBaselineType,
+      subtitleBaselineType: subtitleBaselineType,
     );
   }
 
   @override
-  double computeMaxIntrinsicHeight(double width) {
-    return computeMinIntrinsicHeight(width);
-  }
-
-  @override
-  double computeDistanceToActualBaseline(TextBaseline baseline) {
-    assert(title != null);
-    final BoxParentData parentData = title.parentData;
-    return parentData.offset.dy + title.getDistanceToActualBaseline(TextBaseline.alphabetic);
-  }
-
-  static double _boxBaseline(RenderBox box) {
-    return box.getDistanceToBaseline(TextBaseline.alphabetic);
-  }
-
-  static Size _layoutBox(RenderBox box, BoxConstraints constraints) {
-    if (box == null)
-      return Size.zero;
-    box.layout(constraints, parentUsesSize: true);
-    return box.size;
-  }
-
-  static void _positionBox(RenderBox box, Offset offset) {
-    final BoxParentData parentData = box.parentData;
-    parentData.offset = offset;
-  }
-
-  // All of the dimensions below were taken from the Material Design spec:
-  // https://material.io/design/components/lists.html#specs
-  @override
-  void performLayout() {
-    final bool hasLeading = leading != null;
-    final bool hasSubtitle = subtitle != null;
-    final bool hasTrailing = trailing != null;
-    final bool isTwoLine = !isThreeLine && hasSubtitle;
-    final bool isOneLine = !isThreeLine && !hasSubtitle;
-    final BoxConstraints looseConstraints = constraints.loosen();
-
-    final double tileWidth = looseConstraints.maxWidth;
-    final Size leadingSize = _layoutBox(leading, looseConstraints);
-    final Size trailingSize = _layoutBox(trailing, looseConstraints);
-
-    final double titleStart = hasLeading
-      ? math.max(_minLeadingWidth, leadingSize.width) + _horizontalTitleGap
-      : 0.0;
-    final BoxConstraints textConstraints = looseConstraints.tighten(
-      width: tileWidth - titleStart - (hasTrailing ? trailingSize.width + _horizontalTitleGap : 0.0),
-    );
-    final Size titleSize = _layoutBox(title, textConstraints);
-    final Size subtitleSize = _layoutBox(subtitle, textConstraints);
-
-    double titleBaseline;
-    double subtitleBaseline;
-    if (isTwoLine) {
-      titleBaseline = isDense ? 28.0 : 32.0;
-      subtitleBaseline = isDense ? 48.0 : 52.0;
-    } else if (isThreeLine) {
-      titleBaseline = isDense ? 22.0 : 28.0;
-      subtitleBaseline = isDense ? 42.0 : 48.0;
-    } else {
-      assert(isOneLine);
-    }
-
-    double tileHeight;
-    double titleY;
-    double subtitleY;
-    if (!hasSubtitle) {
-      tileHeight = math.max(_defaultTileHeight, titleSize.height + 2.0 * _minVerticalPadding);
-      titleY = (tileHeight - titleSize.height) / 2.0;
-    } else {
-      titleY = titleBaseline - _boxBaseline(title);
-      subtitleY = subtitleBaseline - _boxBaseline(subtitle);
-      tileHeight = _defaultTileHeight;
-
-      // If the title and subtitle overlap, move the title upwards by half
-      // the overlap and the subtitle down by the same amount, and adjust
-      // tileHeight so that both titles fit.
-      final double titleOverlap = titleY + titleSize.height - subtitleY;
-      if (titleOverlap > 0.0) {
-        titleY -= titleOverlap / 2.0;
-        subtitleY += titleOverlap / 2.0;
-      }
-
-      // If the title or subtitle overflow tileHeight then punt: title
-      // and subtitle are arranged in a column, tileHeight = column height plus
-      // _minVerticalPadding on top and bottom.
-      if (titleY < _minVerticalPadding ||
-          (subtitleY + subtitleSize.height + _minVerticalPadding) > tileHeight) {
-        tileHeight = titleSize.height + subtitleSize.height + 2.0 * _minVerticalPadding;
-        titleY = _minVerticalPadding;
-        subtitleY = titleSize.height + _minVerticalPadding;
-      }
-    }
-
-    final double leadingY = (tileHeight - leadingSize.height) / 2.0;
-    final double trailingY = (tileHeight - trailingSize.height) / 2.0;
-
-    switch (textDirection) {
-      case TextDirection.rtl: {
-        if (hasLeading)
-          _positionBox(leading, new Offset(tileWidth - leadingSize.width, leadingY));
-        final double titleX = hasTrailing ? trailingSize.width + _horizontalTitleGap : 0.0;
-        _positionBox(title, new Offset(titleX, titleY));
-        if (hasSubtitle)
-          _positionBox(subtitle, new Offset(titleX, subtitleY));
-        if (hasTrailing)
-          _positionBox(trailing, new Offset(0.0, trailingY));
-        break;
-      }
-      case TextDirection.ltr: {
-        if (hasLeading)
-          _positionBox(leading, new Offset(0.0, leadingY));
-        _positionBox(title, new Offset(titleStart, titleY));
-        if (hasSubtitle)
-          _positionBox(subtitle, new Offset(titleStart, subtitleY));
-        if (hasTrailing)
-          _positionBox(trailing, new Offset(tileWidth - trailingSize.width, trailingY));
-        break;
-      }
-    }
-
-    size = constraints.constrain(new Size(tileWidth, tileHeight));
-    assert(size.width == constraints.constrainWidth(tileWidth));
-    assert(size.height == constraints.constrainHeight(tileHeight));
-  }
-
-  @override
-  void paint(PaintingContext context, Offset offset) {
-    void doPaint(RenderBox child) {
-      if (child != null) {
-        final BoxParentData parentData = child.parentData;
-        context.paintChild(child, parentData.offset + offset);
-      }
-    }
-    doPaint(leading);
-    doPaint(title);
-    doPaint(subtitle);
-    doPaint(trailing);
-  }
-
-  @override
-  bool hitTestSelf(Offset position) => true;
-
-  @override
-  bool hitTestChildren(HitTestResult result, { @required Offset position }) {
-    assert(position != null);
-    for (RenderBox child in _children) {
-      final BoxParentData parentData = child.parentData;
-      if (child.hitTest(result, position: position - parentData.offset))
-        return true;
-    }
-    return false;
+  void updateRenderObject(BuildContext context, _RenderListTile renderObject) {
+    renderObject
+      ..isThreeLine = isThreeLine
+      ..isDense = isDense
+      ..textDirection = textDirection
+      ..titleBaselineType = titleBaselineType
+      ..subtitleBaselineType = subtitleBaselineType;
   }
 }
 
-class _RenderListTileElement extends RenderObjectElement {
-  _RenderListTileElement(_ListTile widget) : super(widget);
+class _ListTileElement extends RenderObjectElement {
+  _ListTileElement(_ListTile widget) : super(widget);
 
   final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{};
   final Map<Element, _ListTileSlot> childToSlot = <Element, _ListTileSlot>{};
@@ -972,3 +652,374 @@
     assert(false, 'not reachable');
   }
 }
+
+class _RenderListTile extends RenderBox {
+  _RenderListTile({
+    @required bool isDense,
+    @required bool isThreeLine,
+    @required TextDirection textDirection,
+    @required TextBaseline titleBaselineType,
+    TextBaseline subtitleBaselineType,
+  }) : assert(isDense != null),
+       assert(isThreeLine != null),
+       assert(textDirection != null),
+       assert(titleBaselineType != null),
+       _isDense = isDense,
+       _isThreeLine = isThreeLine,
+       _textDirection = textDirection,
+       _titleBaselineType = titleBaselineType,
+       _subtitleBaselineType = subtitleBaselineType;
+
+  static const double _minLeadingWidth = 40.0;
+  // The horizontal gap between the titles and the leading/trailing widgets
+  static const double _horizontalTitleGap = 16.0;
+  // The minimum padding on the top and bottom of the title and subtitle widgets.
+  static const double _minVerticalPadding = 4.0;
+
+  final Map<_ListTileSlot, RenderBox> slotToChild = <_ListTileSlot, RenderBox>{};
+  final Map<RenderBox, _ListTileSlot> childToSlot = <RenderBox, _ListTileSlot>{};
+
+  RenderBox _updateChild(RenderBox oldChild, RenderBox newChild, _ListTileSlot slot) {
+    if (oldChild != null) {
+      dropChild(oldChild);
+      childToSlot.remove(oldChild);
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      childToSlot[newChild] = slot;
+      slotToChild[slot] = newChild;
+      adoptChild(newChild);
+    }
+    return newChild;
+  }
+
+  RenderBox _leading;
+  RenderBox get leading => _leading;
+  set leading(RenderBox value) {
+    _leading = _updateChild(_leading, value, _ListTileSlot.leading);
+  }
+
+  RenderBox _title;
+  RenderBox get title => _title;
+  set title(RenderBox value) {
+    _title = _updateChild(_title, value, _ListTileSlot.title);
+  }
+
+  RenderBox _subtitle;
+  RenderBox get subtitle => _subtitle;
+  set subtitle(RenderBox value) {
+    _subtitle = _updateChild(_subtitle, value, _ListTileSlot.subtitle);
+  }
+
+  RenderBox _trailing;
+  RenderBox get trailing => _trailing;
+  set trailing(RenderBox value) {
+    _trailing = _updateChild(_trailing, value, _ListTileSlot.trailing);
+  }
+
+  // The returned list is ordered for hit testing.
+  Iterable<RenderBox> get _children sync *{
+    if (leading != null)
+      yield leading;
+    if (title != null)
+      yield title;
+    if (subtitle != null)
+      yield subtitle;
+    if (trailing != null)
+      yield trailing;
+  }
+
+  bool get isDense => _isDense;
+  bool _isDense;
+  set isDense(bool value) {
+    assert(value != null);
+    if (_isDense == value)
+      return;
+    _isDense = value;
+    markNeedsLayout();
+  }
+
+  bool get isThreeLine => _isThreeLine;
+  bool _isThreeLine;
+  set isThreeLine(bool value) {
+    assert(value != null);
+    if (_isThreeLine == value)
+      return;
+    _isThreeLine = value;
+    markNeedsLayout();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  TextBaseline get titleBaselineType => _titleBaselineType;
+  TextBaseline _titleBaselineType;
+  set titleBaselineType(TextBaseline value) {
+    assert(value != null);
+    if (_titleBaselineType == value)
+      return;
+    _titleBaselineType = value;
+    markNeedsLayout();
+  }
+
+  TextBaseline get subtitleBaselineType => _subtitleBaselineType;
+  TextBaseline _subtitleBaselineType;
+  set subtitleBaselineType(TextBaseline value) {
+    if (_subtitleBaselineType == value)
+      return;
+    _subtitleBaselineType = value;
+    markNeedsLayout();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    for (RenderBox child in _children)
+      child.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    for (RenderBox child in _children)
+      child.detach();
+  }
+
+  @override
+  void redepthChildren() {
+    _children.forEach(redepthChild);
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    _children.forEach(visitor);
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
+    void add(RenderBox child, String name) {
+      if (child != null)
+        value.add(child.toDiagnosticsNode(name: name));
+    }
+    add(leading, 'leading');
+    add(title, 'title');
+    add(subtitle, 'subtitle');
+    add(trailing, 'trailing');
+    return value;
+  }
+
+  @override
+  bool get sizedByParent => false;
+
+  static double _minWidth(RenderBox box, double height) {
+    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
+  }
+
+  static double _maxWidth(RenderBox box, double height) {
+    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double leadingWidth = leading != null
+      ? math.max(leading.getMinIntrinsicWidth(height), _minLeadingWidth) + _horizontalTitleGap
+      : 0.0;
+    return leadingWidth
+      + math.max(_minWidth(title, height), _minWidth(subtitle, height))
+      + _maxWidth(trailing, height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double leadingWidth = leading != null
+      ? math.max(leading.getMaxIntrinsicWidth(height), _minLeadingWidth) + _horizontalTitleGap
+      : 0.0;
+    return leadingWidth
+      + math.max(_maxWidth(title, height), _maxWidth(subtitle, height))
+      + _maxWidth(trailing, height);
+  }
+
+  double get _defaultTileHeight {
+    final bool hasSubtitle = subtitle != null;
+    final bool isTwoLine = !isThreeLine && hasSubtitle;
+    final bool isOneLine = !isThreeLine && !hasSubtitle;
+
+    if (isOneLine)
+      return isDense ? 48.0 : 56.0;
+    if (isTwoLine)
+      return isDense ? 64.0 : 72.0;
+    return isDense ? 76.0 : 88.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return math.max(
+      _defaultTileHeight,
+      title.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0)
+    );
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return computeMinIntrinsicHeight(width);
+  }
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    assert(title != null);
+    final BoxParentData parentData = title.parentData;
+    return parentData.offset.dy + title.getDistanceToActualBaseline(baseline);
+  }
+
+  static double _boxBaseline(RenderBox box, TextBaseline baseline) {
+    return box.getDistanceToBaseline(baseline);
+  }
+
+  static Size _layoutBox(RenderBox box, BoxConstraints constraints) {
+    if (box == null)
+      return Size.zero;
+    box.layout(constraints, parentUsesSize: true);
+    return box.size;
+  }
+
+  static void _positionBox(RenderBox box, Offset offset) {
+    final BoxParentData parentData = box.parentData;
+    parentData.offset = offset;
+  }
+
+  // All of the dimensions below were taken from the Material Design spec:
+  // https://material.io/design/components/lists.html#specs
+  @override
+  void performLayout() {
+    final bool hasLeading = leading != null;
+    final bool hasSubtitle = subtitle != null;
+    final bool hasTrailing = trailing != null;
+    final bool isTwoLine = !isThreeLine && hasSubtitle;
+    final bool isOneLine = !isThreeLine && !hasSubtitle;
+    final BoxConstraints looseConstraints = constraints.loosen();
+
+    final double tileWidth = looseConstraints.maxWidth;
+    final Size leadingSize = _layoutBox(leading, looseConstraints);
+    final Size trailingSize = _layoutBox(trailing, looseConstraints);
+
+    final double titleStart = hasLeading
+      ? math.max(_minLeadingWidth, leadingSize.width) + _horizontalTitleGap
+      : 0.0;
+    final BoxConstraints textConstraints = looseConstraints.tighten(
+      width: tileWidth - titleStart - (hasTrailing ? trailingSize.width + _horizontalTitleGap : 0.0),
+    );
+    final Size titleSize = _layoutBox(title, textConstraints);
+    final Size subtitleSize = _layoutBox(subtitle, textConstraints);
+
+    double titleBaseline;
+    double subtitleBaseline;
+    if (isTwoLine) {
+      titleBaseline = isDense ? 28.0 : 32.0;
+      subtitleBaseline = isDense ? 48.0 : 52.0;
+    } else if (isThreeLine) {
+      titleBaseline = isDense ? 22.0 : 28.0;
+      subtitleBaseline = isDense ? 42.0 : 48.0;
+    } else {
+      assert(isOneLine);
+    }
+
+    double tileHeight;
+    double titleY;
+    double subtitleY;
+    if (!hasSubtitle) {
+      tileHeight = math.max(_defaultTileHeight, titleSize.height + 2.0 * _minVerticalPadding);
+      titleY = (tileHeight - titleSize.height) / 2.0;
+    } else {
+      assert(subtitleBaselineType != null);
+      titleY = titleBaseline - _boxBaseline(title, titleBaselineType);
+      subtitleY = subtitleBaseline - _boxBaseline(subtitle, subtitleBaselineType);
+      tileHeight = _defaultTileHeight;
+
+      // If the title and subtitle overlap, move the title upwards by half
+      // the overlap and the subtitle down by the same amount, and adjust
+      // tileHeight so that both titles fit.
+      final double titleOverlap = titleY + titleSize.height - subtitleY;
+      if (titleOverlap > 0.0) {
+        titleY -= titleOverlap / 2.0;
+        subtitleY += titleOverlap / 2.0;
+      }
+
+      // If the title or subtitle overflow tileHeight then punt: title
+      // and subtitle are arranged in a column, tileHeight = column height plus
+      // _minVerticalPadding on top and bottom.
+      if (titleY < _minVerticalPadding ||
+          (subtitleY + subtitleSize.height + _minVerticalPadding) > tileHeight) {
+        tileHeight = titleSize.height + subtitleSize.height + 2.0 * _minVerticalPadding;
+        titleY = _minVerticalPadding;
+        subtitleY = titleSize.height + _minVerticalPadding;
+      }
+    }
+
+    final double leadingY = (tileHeight - leadingSize.height) / 2.0;
+    final double trailingY = (tileHeight - trailingSize.height) / 2.0;
+
+    switch (textDirection) {
+      case TextDirection.rtl: {
+        if (hasLeading)
+          _positionBox(leading, new Offset(tileWidth - leadingSize.width, leadingY));
+        final double titleX = hasTrailing ? trailingSize.width + _horizontalTitleGap : 0.0;
+        _positionBox(title, new Offset(titleX, titleY));
+        if (hasSubtitle)
+          _positionBox(subtitle, new Offset(titleX, subtitleY));
+        if (hasTrailing)
+          _positionBox(trailing, new Offset(0.0, trailingY));
+        break;
+      }
+      case TextDirection.ltr: {
+        if (hasLeading)
+          _positionBox(leading, new Offset(0.0, leadingY));
+        _positionBox(title, new Offset(titleStart, titleY));
+        if (hasSubtitle)
+          _positionBox(subtitle, new Offset(titleStart, subtitleY));
+        if (hasTrailing)
+          _positionBox(trailing, new Offset(tileWidth - trailingSize.width, trailingY));
+        break;
+      }
+    }
+
+    size = constraints.constrain(new Size(tileWidth, tileHeight));
+    assert(size.width == constraints.constrainWidth(tileWidth));
+    assert(size.height == constraints.constrainHeight(tileHeight));
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    void doPaint(RenderBox child) {
+      if (child != null) {
+        final BoxParentData parentData = child.parentData;
+        context.paintChild(child, parentData.offset + offset);
+      }
+    }
+    doPaint(leading);
+    doPaint(title);
+    doPaint(subtitle);
+    doPaint(trailing);
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  bool hitTestChildren(HitTestResult result, { @required Offset position }) {
+    assert(position != null);
+    for (RenderBox child in _children) {
+      final BoxParentData parentData = child.parentData;
+      if (child.hitTest(result, position: position - parentData.offset))
+        return true;
+    }
+    return false;
+  }
+}
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart
index 81abc55..0d1e773 100644
--- a/packages/flutter/lib/src/material/popup_menu.dart
+++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -248,7 +248,7 @@
       duration: kThemeChangeDuration,
       child: new Baseline(
         baseline: widget.height - _kBaselineOffsetFromBottom,
-        baselineType: TextBaseline.alphabetic,
+        baselineType: style.textBaseline,
         child: buildChild(),
       )
     );
diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart
index 322de44..5c0f7c2 100644
--- a/packages/flutter/lib/src/rendering/box.dart
+++ b/packages/flutter/lib/src/rendering/box.dart
@@ -1599,9 +1599,12 @@
   /// Only call this function after calling [layout] on this box. You
   /// are only allowed to call this from the parent of this box during
   /// that parent's [performLayout] or [paint] functions.
+  ///
+  /// When implementing a [RenderBox] subclass, to override the baseline
+  /// computation, override [computeDistanceToActualBaseline].
   double getDistanceToBaseline(TextBaseline baseline, { bool onlyReal: false }) {
+    assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
     assert(!debugNeedsLayout);
-    assert(!_debugDoingBaseline);
     assert(() {
       final RenderObject parent = this.parent;
       if (owner.debugDoingLayout)
@@ -1628,7 +1631,7 @@
   @protected
   @mustCallSuper
   double getDistanceToActualBaseline(TextBaseline baseline) {
-    assert(_debugDoingBaseline);
+    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
     _cachedBaselines ??= <TextBaseline, double>{};
     _cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
     return _cachedBaselines[baseline];
@@ -1638,17 +1641,29 @@
   /// the y-coordinate of the first given baseline in the box's contents, if
   /// any, or null otherwise.
   ///
-  /// Do not call this function directly. Instead, call [getDistanceToBaseline]
-  /// if you need to know the baseline of a child from an invocation of
-  /// [performLayout] or [paint] and call [getDistanceToActualBaseline] if you
-  /// are implementing [computeDistanceToActualBaseline] and need to defer to a
-  /// child.
+  /// Do not call this function directly. If you need to know the baseline of a
+  /// child from an invocation of [performLayout] or [paint], call
+  /// [getDistanceToBaseline].
   ///
   /// Subclasses should override this method to supply the distances to their
-  /// baselines.
+  /// baselines. When implementing this method, there are generally three
+  /// strategies:
+  ///
+  ///  * For classes that use the [ContainerRenderObjectMixin] child model,
+  ///    consider mixing in the [RenderBoxContainerDefaultsMixin] class and
+  ///    using
+  ///    [RenderBoxContainerDefaultsMixin.defaultComputeDistanceToFirstActualBaseline].
+  ///
+  ///  * For classes that define a particular baseline themselves, return that
+  ///    value directly.
+  ///
+  ///  * For classes that have a child to which they wish to defer the
+  ///    computation, call [getDistanceToActualBaseline] on the child (not
+  ///    [computeDistanceToActualBaseline], the internal implementation, and not
+  ///    [getDistanceToBaseline], the public entry point for this API).
   @protected
   double computeDistanceToActualBaseline(TextBaseline baseline) {
-    assert(_debugDoingBaseline);
+    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
     return null;
   }
 
diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart
index d6e5777..3ffcc40 100644
--- a/packages/flutter/lib/src/rendering/shifted_box.dart
+++ b/packages/flutter/lib/src/rendering/shifted_box.dart
@@ -1124,7 +1124,7 @@
   RenderBaseline({
     RenderBox child,
     @required double baseline,
-    @required TextBaseline baselineType
+    @required TextBaseline baselineType,
   }) : assert(baseline != null),
        assert(baselineType != null),
        _baseline = baseline,
diff --git a/packages/flutter/lib/src/widgets/table.dart b/packages/flutter/lib/src/widgets/table.dart
index 7d3c86d..fcc51d3 100644
--- a/packages/flutter/lib/src/widgets/table.dart
+++ b/packages/flutter/lib/src/widgets/table.dart
@@ -101,7 +101,7 @@
     this.textDirection,
     this.border,
     this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
-    this.textBaseline
+    this.textBaseline,
   }) : assert(children != null),
        assert(defaultColumnWidth != null),
        assert(defaultVerticalAlignment != null),
@@ -213,7 +213,7 @@
       rowDecorations: _rowDecorations,
       configuration: createLocalImageConfiguration(context),
       defaultVerticalAlignment: defaultVerticalAlignment,
-      textBaseline: textBaseline
+      textBaseline: textBaseline,
     );
   }
 
diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart
index 02b33f7..b642b06 100644
--- a/packages/flutter/test/material/chip_test.dart
+++ b/packages/flutter/test/material/chip_test.dart
@@ -65,18 +65,11 @@
   double textScaleFactor: 1.0,
 }) {
   return new MaterialApp(
-    home: new Localizations(
-      locale: const Locale('en', 'US'),
-      delegates: const <LocalizationsDelegate<dynamic>>[
-        DefaultWidgetsLocalizations.delegate,
-        DefaultMaterialLocalizations.delegate,
-      ],
-      child: new Directionality(
-        textDirection: textDirection,
-        child: new MediaQuery(
-          data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
-          child: new Material(child: child),
-        ),
+    home: new Directionality(
+      textDirection: textDirection,
+      child: new MediaQuery(
+        data: new MediaQueryData.fromWindow(window).copyWith(textScaleFactor: textScaleFactor),
+        child: new Material(child: child),
       ),
     ),
   );
diff --git a/packages/flutter/test/widgets/baseline_test.dart b/packages/flutter/test/widgets/baseline_test.dart
index 681600a..03875b5 100644
--- a/packages/flutter/test/widgets/baseline_test.dart
+++ b/packages/flutter/test/widgets/baseline_test.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter_test/flutter_test.dart';
-import 'package:flutter/widgets.dart';
+import 'package:flutter/material.dart';
 
 void main() {
   testWidgets('Baseline - control test', (WidgetTester tester) async {
@@ -41,4 +41,108 @@
     expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size,
            within<Size>(from: const Size(100.0, 200.0), distance: 0.001));
   });
+
+  testWidgets('Chip caches baseline', (WidgetTester tester) async {
+    int calls = 0;
+    await tester.pumpWidget(
+      new MaterialApp(
+        home: new Material(
+          child: new Baseline(
+            baseline: 100.0,
+            baselineType: TextBaseline.alphabetic,
+            child: new Chip(
+              label: new BaselineDetector(() {
+                calls += 1;
+              }),
+            ),
+          ),
+        ),
+      ),
+    );
+    expect(calls, 1);
+    await tester.pump();
+    expect(calls, 1);
+    tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
+    await tester.pump();
+    expect(calls, 2);
+  });
+
+  testWidgets('ListTile caches baseline', (WidgetTester tester) async {
+    int calls = 0;
+    await tester.pumpWidget(
+      new MaterialApp(
+        home: new Material(
+          child: new Baseline(
+            baseline: 100.0,
+            baselineType: TextBaseline.alphabetic,
+            child: new ListTile(
+              title: new BaselineDetector(() {
+                calls += 1;
+              }),
+            ),
+          ),
+        ),
+      ),
+    );
+    expect(calls, 1);
+    await tester.pump();
+    expect(calls, 1);
+    tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
+    await tester.pump();
+    expect(calls, 2);
+  });
+}
+
+class BaselineDetector extends LeafRenderObjectWidget {
+  const BaselineDetector(this.callback);
+
+  final VoidCallback callback;
+
+  @override
+  RenderBaselineDetector createRenderObject(BuildContext context) => new RenderBaselineDetector(callback);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderBaselineDetector renderObject) {
+    renderObject.callback = callback;
+  }
+}
+
+class RenderBaselineDetector extends RenderBox {
+  RenderBaselineDetector(this.callback);
+
+  VoidCallback callback;
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  double computeMinIntrinsicWidth(double height) => 0.0;
+
+  @override
+  double computeMaxIntrinsicWidth(double height) => 0.0;
+
+  @override
+  double computeMinIntrinsicHeight(double width) => 0.0;
+
+  @override
+  double computeMaxIntrinsicHeight(double width) => 0.0;
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    if (callback != null)
+      callback();
+    return 0.0;
+  }
+
+  void dirty() {
+    markNeedsLayout();
+  }
+
+  @override
+  void performResize() {
+    size = constraints.smallest;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) { }
 }