diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index 0bfa596c19..469e0893ba 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -2,11 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math' as math; - import 'package:flutter/widgets.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; import 'colors.dart'; import 'constants.dart'; @@ -46,7 +42,6 @@ class ListTileTheme extends InheritedWidget { this.selectedColor, this.iconColor, this.textColor, - this.contentPadding, Widget child, }) : super(key: key, child: child); @@ -61,7 +56,6 @@ class ListTileTheme extends InheritedWidget { Color selectedColor, Color iconColor, Color textColor, - EdgeInsetsGeometry contentPadding, @required Widget child, }) { assert(child != null); @@ -75,7 +69,6 @@ class ListTileTheme extends InheritedWidget { selectedColor: selectedColor ?? parent.selectedColor, iconColor: iconColor ?? parent.iconColor, textColor: textColor ?? parent.textColor, - contentPadding: contentPadding ?? parent.contentPadding, child: child, ); }, @@ -97,12 +90,6 @@ class ListTileTheme extends InheritedWidget { /// If specified, the text color used for enabled [ListTile]s that are not selected. final Color textColor; - /// The tile's internal padding. - /// - /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], - /// and [trailing] widgets. - final EdgeInsetsGeometry contentPadding; - /// The closest instance of this class that encloses the given context. /// /// Typical usage is as follows: @@ -121,8 +108,7 @@ class ListTileTheme extends InheritedWidget { || style != oldWidget.style || selectedColor != oldWidget.selectedColor || iconColor != oldWidget.iconColor - || textColor != oldWidget.textColor - || contentPadding != oldWidget.contentPadding; + || textColor != oldWidget.textColor; } } @@ -224,7 +210,6 @@ class ListTile extends StatelessWidget { this.trailing, this.isThreeLine: false, this.dense, - this.contentPadding, this.enabled: true, this.onTap, this.onLongPress, @@ -266,14 +251,6 @@ class ListTile extends StatelessWidget { /// If this property is null then its value is based on [ListTileTheme.dense]. final bool dense; - /// The tile's internal padding. - /// - /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], - /// and [trailing] widgets. - /// - /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. - final EdgeInsetsGeometry contentPadding; - /// Whether this list tile is interactive. /// /// If false, this list tile is styled with the disabled color from the @@ -370,7 +347,7 @@ class ListTile extends StatelessWidget { return defaultColor; } - bool _isDenseLayout(ListTileTheme tileTheme) { + bool _denseLayout(ListTileTheme tileTheme) { return dense != null ? dense : (tileTheme?.dense ?? false); } @@ -389,7 +366,7 @@ class ListTile extends StatelessWidget { style = theme.textTheme.subhead; } final Color color = _textColor(theme, tileTheme, style.color); - return _isDenseLayout(tileTheme) + return _denseLayout(tileTheme) ? style.copyWith(fontSize: 13.0, color: color) : style.copyWith(color: color); } @@ -397,7 +374,7 @@ class ListTile extends StatelessWidget { TextStyle _subtitleTextStyle(ThemeData theme, ListTileTheme tileTheme) { final TextStyle style = theme.textTheme.body1; final Color color = _textColor(theme, tileTheme, theme.textTheme.caption.color); - return _isDenseLayout(tileTheme) + return _denseLayout(tileTheme) ? style.copyWith(color: color, fontSize: 12.0) : style.copyWith(color: color); } @@ -408,563 +385,91 @@ class ListTile extends StatelessWidget { final ThemeData theme = Theme.of(context); final ListTileTheme tileTheme = ListTileTheme.of(context); + final bool isTwoLine = !isThreeLine && subtitle != null; + final bool isOneLine = !isThreeLine && !isTwoLine; + double tileHeight; + if (isOneLine) + tileHeight = _denseLayout(tileTheme) ? 48.0 : 56.0; + else if (isTwoLine) + tileHeight = _denseLayout(tileTheme) ? 60.0 : 72.0; + else + tileHeight = _denseLayout(tileTheme) ? 76.0 : 88.0; + + // Overall, the list tile is a Row() with these children. + final List children = []; + IconThemeData iconThemeData; if (leading != null || trailing != null) iconThemeData = new IconThemeData(color: _iconColor(theme, tileTheme)); - Widget leadingIcon; if (leading != null) { - leadingIcon = IconTheme.merge( + children.add(IconTheme.merge( data: iconThemeData, - child: leading, - ); + child: new Container( + margin: const EdgeInsetsDirectional.only(end: 16.0), + width: 40.0, + alignment: AlignmentDirectional.centerStart, + child: leading, + ), + )); } - final Widget titleText = new AnimatedDefaultTextStyle( + final Widget primaryLine = new AnimatedDefaultTextStyle( style: _titleTextStyle(theme, tileTheme), duration: kThemeChangeDuration, - child: title ?? const SizedBox() + child: title ?? new Container() ); - - Widget subtitleText; - if (subtitle != null) { - subtitleText = new AnimatedDefaultTextStyle( - style: _subtitleTextStyle(theme, tileTheme), - duration: kThemeChangeDuration, - child: subtitle, + Widget center = primaryLine; + if (subtitle != null && (isTwoLine || isThreeLine)) { + center = new Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + primaryLine, + new AnimatedDefaultTextStyle( + style: _subtitleTextStyle(theme, tileTheme), + duration: kThemeChangeDuration, + child: subtitle, + ), + ], ); } + children.add(new Expanded( + child: center, + )); - Widget trailingIcon; if (trailing != null) { - trailingIcon = IconTheme.merge( + children.add(IconTheme.merge( data: iconThemeData, - child: trailing, - ); + child: new Container( + margin: const EdgeInsetsDirectional.only(start: 16.0), + alignment: AlignmentDirectional.centerEnd, + child: trailing, + ), + )); } - const EdgeInsets _kDefaultContentPadding = const EdgeInsets.symmetric(horizontal: 16.0); - final TextDirection textDirection = Directionality.of(context); - final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection) - ?? tileTheme?.contentPadding?.resolve(textDirection) - ?? _kDefaultContentPadding; - return new InkWell( onTap: enabled ? onTap : null, onLongPress: enabled ? onLongPress : null, child: new Semantics( selected: selected, enabled: enabled, - child: new SafeArea( - top: false, - bottom: false, - minimum: resolvedContentPadding, - child: new _ListTile( - leading: leadingIcon, - title: titleText, - subtitle: subtitleText, - trailing: trailingIcon, - isDense: _isDenseLayout(tileTheme), - isThreeLine: isThreeLine, - ), + child: new ConstrainedBox( + constraints: new BoxConstraints(minHeight: tileHeight), + child: new Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: new UnconstrainedBox( + constrainedAxis: Axis.horizontal, + child: new SafeArea( + top: false, + bottom: false, + child: new Row(children: children), + ), + ), + ) ), ), ); } } - -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, - title, - subtitle, - trailing, -} - -class _RenderListTile extends RenderBox { - _RenderListTile({ - bool isDense, - bool isThreeLine, - TextDirection textDirection, - }) : _isDense = isDense, - _isThreeLine = isThreeLine, - _textDirection = textDirection; - - static const double _kMinLeadingWidth = 40.0; - static const double _kTitleGap = 16.0; // between the titles and the leading/trailing widgets - - final Map<_ListTileSlot, RenderBox> slotToChild = <_ListTileSlot, RenderBox>{}; - final Map childToSlot = {}; - - 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 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(); - } - - @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 debugDescribeChildren() { - final List value = []; - 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), _kMinLeadingWidth) + _kTitleGap - : 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), _kMinLeadingWidth) + _kTitleGap - : 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; - else if (isTwoLine) - return isDense ? 64.0 : 72.0; - else - 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.getDistanceToBaseline(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(_kMinLeadingWidth, leadingSize.width) + _kTitleGap - : 0.0; - final BoxConstraints textConstraints = looseConstraints.tighten( - width: tileWidth - titleStart - (hasTrailing ? trailingSize.width + _kTitleGap : 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); - 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. - if (titleY < 0.0 || subtitleY > tileHeight) { - tileHeight = titleSize.height + subtitleSize.height; - titleY = 0.0; - subtitleY = titleSize.height; - } - } - - 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 + _kTitleGap : 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; - } -} - -class _RenderListTileElement extends RenderObjectElement { - _RenderListTileElement(_ListTile widget) : super(widget); - - final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{}; - final Map childToSlot = {}; - - @override - _ListTile get widget => super.widget; - - @override - _RenderListTile get renderObject => super.renderObject; - - @override - void visitChildren(ElementVisitor visitor) { - slotToChild.values.forEach(visitor); - } - - @override - void forgetChild(Element child) { - assert(slotToChild.values.contains(child)); - assert(childToSlot.keys.contains(child)); - final _ListTileSlot slot = childToSlot[child]; - childToSlot.remove(child); - slotToChild.remove(slot); - } - - void _mountChild(Widget widget, _ListTileSlot slot) { - final Element oldChild = slotToChild[slot]; - final Element newChild = updateChild(oldChild, widget, slot); - if (oldChild != null) { - slotToChild.remove(slot); - childToSlot.remove(oldChild); - } - if (newChild != null) { - slotToChild[slot] = newChild; - childToSlot[newChild] = slot; - } - } - - @override - void mount(Element parent, dynamic newSlot) { - super.mount(parent, newSlot); - _mountChild(widget.leading, _ListTileSlot.leading); - _mountChild(widget.title, _ListTileSlot.title); - _mountChild(widget.subtitle, _ListTileSlot.subtitle); - _mountChild(widget.trailing, _ListTileSlot.trailing); - } - - void _updateChild(Widget widget, _ListTileSlot slot) { - final Element oldChild = slotToChild[slot]; - final Element newChild = updateChild(oldChild, widget, slot); - if (oldChild != null) { - childToSlot.remove(oldChild); - slotToChild.remove(slot); - } - if (newChild != null) { - slotToChild[slot] = newChild; - childToSlot[newChild] = slot; - } - } - - @override - void update(_ListTile newWidget) { - super.update(newWidget); - assert(widget == newWidget); - _updateChild(widget.leading, _ListTileSlot.leading); - _updateChild(widget.title, _ListTileSlot.title); - _updateChild(widget.subtitle, _ListTileSlot.subtitle); - _updateChild(widget.trailing, _ListTileSlot.trailing); - } - - void _updateRenderObject(RenderObject child, _ListTileSlot slot) { - switch (slot) { - case _ListTileSlot.leading: - renderObject.leading = child; - break; - case _ListTileSlot.title: - renderObject.title = child; - break; - case _ListTileSlot.subtitle: - renderObject.subtitle = child; - break; - case _ListTileSlot.trailing: - renderObject.trailing = child; - break; - } - } - - @override - void insertChildRenderObject(RenderObject child, dynamic slotValue) { - assert(child is RenderBox); - assert(slotValue is _ListTileSlot); - final _ListTileSlot slot = slotValue; - _updateRenderObject(child, slot); - assert(renderObject.childToSlot.keys.contains(child)); - assert(renderObject.slotToChild.keys.contains(slot)); - } - - @override - void removeChildRenderObject(RenderObject child) { - assert(child is RenderBox); - assert(renderObject.childToSlot.keys.contains(child)); - _updateRenderObject(null, renderObject.childToSlot[child]); - assert(!renderObject.childToSlot.keys.contains(child)); - assert(!renderObject.slotToChild.keys.contains(slot)); - } - - @override - void moveChildRenderObject(RenderObject child, dynamic slotValue) { - assert(false, 'not reachable'); - } -} diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index a2b9e4f49c..36030bf559 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math' as math; - import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -97,23 +95,23 @@ void main() { double widthKey(Key key) => tester.getSize(find.byKey(key)).width; double heightKey(Key key) => tester.getSize(find.byKey(key)).height; - // ListTiles are contained by a SafeArea defined like this: - // SafeArea(top: false, bottom: false, minimim: contentPadding) - // The default contentPadding is 16.0 on the left and right. + + // 16.0 padding to the left and right of the leading and trailing widgets + // plus the media padding. void testHorizontalGeometry() { - expect(leftKey(leadingKey), math.max(16.0, leftPadding)); - expect(left('title'), 56.0 + math.max(16.0, leftPadding)); + expect(leftKey(leadingKey), 16.0 + leftPadding); + expect(left('title'), 72.0 + leftPadding); if (hasSubtitle) - expect(left('subtitle'), 56.0 + math.max(16.0, leftPadding)); + expect(left('subtitle'), 72.0 + leftPadding); expect(left('title'), rightKey(leadingKey) + 32.0); - expect(rightKey(trailingKey), 800.0 - math.max(16.0, rightPadding)); + expect(rightKey(trailingKey), 800.0 - 16.0 - rightPadding); expect(widthKey(trailingKey), 24.0); } void testVerticalGeometry(double expectedHeight) { expect(tester.getSize(find.byType(ListTile)), new Size(800.0, expectedHeight)); if (hasSubtitle) - expect(top('subtitle'), greaterThanOrEqualTo(bottom('title'))); + expect(top('subtitle'), bottom('title')); expect(heightKey(trailingKey), 24.0); } @@ -135,7 +133,7 @@ void main() { await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true)); testChildren(); testHorizontalGeometry(); - testVerticalGeometry(64.0); + testVerticalGeometry(60.0); await tester.pumpWidget(buildFrame(isThreeLine: true)); testChildren(); @@ -191,9 +189,9 @@ void main() { child: const Material( child: const Center( child: const ListTile( - leading: const Text('L'), + leading: const Text('leading'), title: const Text('title'), - trailing: const Text('T'), + trailing: const Text('trailing'), ), ), ), @@ -204,9 +202,10 @@ void main() { double right(String text) => tester.getTopRight(find.text(text)).dx; void testHorizontalGeometry() { - expect(right('L'), 800.0 - math.max(16.0, rightPadding)); - expect(right('title'), 800.0 - 56.0 - math.max(16.0, rightPadding)); - expect(left('T'), math.max(16.0, leftPadding)); + expect(right('leading'), 800.0 - 16.0 - rightPadding); + expect(right('title'), 800.0 - 72.0 - rightPadding); + expect(left('leading') - right('title'), 16.0); + expect(left('trailing'), 16.0 + leftPadding); } testHorizontalGeometry(); @@ -386,165 +385,4 @@ void main() { semantics.dispose(); }); - - testWidgets('ListTile contentPadding', (WidgetTester tester) async { - Widget buildFrame(TextDirection textDirection) { - return new MediaQuery( - data: const MediaQueryData( - padding: EdgeInsets.zero, - textScaleFactor: 1.0 - ), - child: new Directionality( - textDirection: textDirection, - child: new Material( - child: new Container( - alignment: Alignment.topLeft, - child: const ListTile( - contentPadding: const EdgeInsetsDirectional.only( - start: 10.0, - end: 20.0, - top: 30.0, - bottom: 40.0, - ), - leading: const Text('L'), - title: const Text('title'), - trailing: const Text('T'), - ), - ), - ), - ), - ); - } - - double left(String text) => tester.getTopLeft(find.text(text)).dx; - double right(String text) => tester.getTopRight(find.text(text)).dx; - - await tester.pumpWidget(buildFrame(TextDirection.ltr)); - - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40 - expect(left('L'), 10.0); // contentPadding.start = 10 - expect(right('T'), 780.0); // 800 - contentPadding.end - - await tester.pumpWidget(buildFrame(TextDirection.rtl)); - - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40 - expect(left('T'), 20.0); // contentPadding.end = 20 - expect(right('L'), 790.0); // 800 - contentPadding.start - }); - - testWidgets('ListTile contentPadding', (WidgetTester tester) async { - Widget buildFrame(TextDirection textDirection) { - return new MediaQuery( - data: const MediaQueryData( - padding: EdgeInsets.zero, - textScaleFactor: 1.0 - ), - child: new Directionality( - textDirection: textDirection, - child: new Material( - child: new Container( - alignment: Alignment.topLeft, - child: const ListTile( - contentPadding: const EdgeInsetsDirectional.only( - start: 10.0, - end: 20.0, - top: 30.0, - bottom: 40.0, - ), - leading: const Text('L'), - title: const Text('title'), - trailing: const Text('T'), - ), - ), - ), - ), - ); - } - - double left(String text) => tester.getTopLeft(find.text(text)).dx; - double right(String text) => tester.getTopRight(find.text(text)).dx; - - await tester.pumpWidget(buildFrame(TextDirection.ltr)); - - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40 - expect(left('L'), 10.0); // contentPadding.start = 10 - expect(right('T'), 780.0); // 800 - contentPadding.end - - await tester.pumpWidget(buildFrame(TextDirection.rtl)); - - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40 - expect(left('T'), 20.0); // contentPadding.end = 20 - expect(right('L'), 790.0); // 800 - contentPadding.start - }); - - - testWidgets('ListTileTheme wide leading Widget', (WidgetTester tester) async { - const Key leadingKey = const ValueKey('L'); - - Widget buildFrame(double leadingWidth, TextDirection textDirection) { - return new MediaQuery( - data: const MediaQueryData( - padding: EdgeInsets.zero, - textScaleFactor: 1.0 - ), - child: new Directionality( - textDirection: textDirection, - child: new Material( - child: new Container( - alignment: Alignment.topLeft, - child: new ListTile( - contentPadding: EdgeInsets.zero, - leading: new SizedBox(key: leadingKey, width: leadingWidth, height: 32.0), - title: const Text('title'), - subtitle: const Text('subtitle'), - ), - ), - ), - ), - ); - } - - double left(String text) => tester.getTopLeft(find.text(text)).dx; - double right(String text) => tester.getTopRight(find.text(text)).dx; - - // textDirection = LTR - - // Two-line tile's height = 72, leading 24x32 widget is vertically centered - await tester.pumpWidget(buildFrame(24.0, TextDirection.ltr)); - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0)); - expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 20.0)); - expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(24.0, 52.0)); - - // Leading widget's width is 20, so default layout: the left edges of the - // title and subtitle are at 56dps (contentPadding is zero). - expect(left('title'), 56.0); - expect(left('subtitle'), 56.0); - - // If the leading widget is wider than 40 it is separated from the - // title and subtitle by 16. - await tester.pumpWidget(buildFrame(56.0, TextDirection.ltr)); - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0)); - expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 20.0)); - expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(56.0, 52.0)); - expect(left('title'), 72.0); - expect(left('subtitle'), 72.0); - - // Same tests, textDirection = RTL - - await tester.pumpWidget(buildFrame(24.0, TextDirection.rtl)); - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0)); - expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 20.0)); - expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 24.0, 52.0)); - expect(right('title'), 800.0 - 56.0); - expect(right('subtitle'), 800.0 - 56.0); - - await tester.pumpWidget(buildFrame(56.0, TextDirection.rtl)); - expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0)); - expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 20.0)); - expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 56.0, 52.0)); - expect(right('title'), 800.0 - 72.0); - expect(right('subtitle'), 800.0 - 72.0); - - }); - }