Unverified Commit c15c021e authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Adding accomodations to ListTile for scaleTextFactor. (#12973)

This makes ListTile expand vertically when text is scaled, or really when whatever is placed inside it is larger than the available space.

In order for UnconstrainedBox to be useful here, I needed for it to only unconstrain the child Row in one dimension, so I added a "constrainedAxis" parameter to the UnconstrainedBox.

Also, changed one enum test to use a switch instead.

I modified the ListTile test to be more representative of the intention of the spec: we were testing with text in the leading and trailing sections, and the design wants icons there. Because there was leading text, and the dense mode only changes the font size on the text lines, the leading text was propping up the minimum size of the tile, making so that the test wasn't really testing any changes in dense mode.
parent 9a0d4cf7
......@@ -351,9 +351,19 @@ class ListTile extends StatelessWidget {
}
TextStyle _titleTextStyle(ThemeData theme, ListTileTheme tileTheme) {
final TextStyle style = tileTheme?.style == ListTileStyle.drawer
? theme.textTheme.body2
: theme.textTheme.subhead;
TextStyle style;
if (tileTheme != null) {
switch (tileTheme.style) {
case ListTileStyle.drawer:
style = theme.textTheme.body2;
break;
case ListTileStyle.list:
style = theme.textTheme.subhead;
break;
}
} else {
style = theme.textTheme.subhead;
}
final Color color = _textColor(theme, tileTheme, style.color);
return _denseLayout(tileTheme)
? style.copyWith(fontSize: 13.0, color: color)
......@@ -441,11 +451,16 @@ class ListTile extends StatelessWidget {
return new InkWell(
onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null,
child: new Container(
height: tileHeight,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new Row(children: children),
)
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 Row(children: children),
),
)
),
);
}
}
......@@ -607,10 +607,28 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
RenderUnconstrainedBox({
@required AlignmentGeometry alignment,
@required TextDirection textDirection,
Axis constrainedAxis,
RenderBox child,
}) : assert(alignment != null),
super.mixin(alignment, textDirection, child);
_constrainedAxis = constrainedAxis,
super.mixin(alignment, textDirection, child);
/// The axis to retain constraints on, if any.
///
/// If not set, or set to null (the default), neither axis will retain its
/// constraints. If set to [Axis.vertical], then vertical constraints will
/// be retained, and if set to [Axis.horizontal], then horizontal constraints
/// will be retained.
Axis get constrainedAxis => _constrainedAxis;
Axis _constrainedAxis;
set constrainedAxis(Axis value) {
assert(value != null);
if (_constrainedAxis == value)
return;
_constrainedAxis = value;
markNeedsLayout();
}
Rect _overflowContainerRect = Rect.zero;
Rect _overflowChildRect = Rect.zero;
bool _isOverflowing = false;
......@@ -618,8 +636,26 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
@override
void performLayout() {
if (child != null) {
// Let the child lay itself out at it's "natural" size.
child.layout(const BoxConstraints(), parentUsesSize: true);
// Let the child lay itself out at it's "natural" size, but if
// constrainedAxis is non-null, keep any constraints on that axis.
if (constrainedAxis != null) {
switch (constrainedAxis) {
case Axis.horizontal:
child.layout(new BoxConstraints(
maxWidth: constraints.maxWidth, minWidth: constraints.minWidth),
parentUsesSize: true,
);
break;
case Axis.vertical:
child.layout(new BoxConstraints(
maxHeight: constraints.maxHeight, minHeight: constraints.minHeight),
parentUsesSize: true,
);
break;
}
} else {
child.layout(const BoxConstraints(), parentUsesSize: true);
}
size = constraints.constrain(child.size);
alignChild();
final BoxParentData childParentData = child.parentData;
......
......@@ -1644,6 +1644,7 @@ class UnconstrainedBox extends SingleChildRenderObjectWidget {
Widget child,
this.textDirection,
this.alignment: Alignment.center,
this.constrainedAxis,
}) : assert(alignment != null),
super(key: key, child: child);
......@@ -1662,23 +1663,34 @@ class UnconstrainedBox extends SingleChildRenderObjectWidget {
/// * [AlignmentDirectional] for [Directionality]-aware alignments.
final AlignmentGeometry alignment;
/// The axis to retain constraints on, if any.
///
/// If not set, or set to null (the default), neither axis will retain its
/// constraints. If set to [Axis.vertical], then vertical constraints will
/// be retained, and if set to [Axis.horizontal], then horizontal constraints
/// will be retained.
final Axis constrainedAxis;
@override
void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) {
renderObject
..textDirection = textDirection ?? Directionality.of(context)
..alignment = alignment;
..alignment = alignment
..constrainedAxis = constrainedAxis;
}
@override
RenderUnconstrainedBox createRenderObject(BuildContext context) => new RenderUnconstrainedBox(
textDirection: textDirection ?? Directionality.of(context),
alignment: alignment,
constrainedAxis: constrainedAxis,
);
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
description.add(new DiagnosticsProperty<Axis>('constrainedAxis', null));
description.add(new DiagnosticsProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
}
}
......
......@@ -45,20 +45,25 @@ void main() {
testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async {
// See https://material.io/guidelines/components/lists.html
final Key leadingKey = new GlobalKey();
final Key trailingKey = new GlobalKey();
bool hasSubtitle;
Widget buildFrame({ bool dense: false, bool isTwoLine: false, bool isThreeLine: false }) {
Widget buildFrame({ bool dense: false, bool isTwoLine: false, bool isThreeLine: false, double textScaleFactor: 1.0 }) {
hasSubtitle = isTwoLine || isThreeLine;
return new MaterialApp(
home: new Material(
child: new Center(
child: new ListTile(
leading: const Text('leading'),
title: const Text('title'),
subtitle: hasSubtitle ? const Text('subtitle') : null,
trailing: const Text('trailing'),
dense: dense,
isThreeLine: isThreeLine,
home: new MediaQuery(
data: new MediaQueryData(textScaleFactor: textScaleFactor),
child: new Material(
child: new Center(
child: new ListTile(
leading: new Container(key: leadingKey, width: 24.0, height: 24.0),
title: const Text('title'),
subtitle: hasSubtitle ? const Text('subtitle') : null,
trailing: new Container(key: trailingKey, width: 24.0, height: 24.0),
dense: dense,
isThreeLine: isThreeLine,
),
),
),
),
......@@ -66,36 +71,39 @@ void main() {
}
void testChildren() {
expect(find.text('leading'), findsOneWidget);
expect(find.byKey(leadingKey), findsOneWidget);
expect(find.text('title'), findsOneWidget);
if (hasSubtitle)
expect(find.text('subtitle'), findsOneWidget);
expect(find.text('trailing'), findsOneWidget);
expect(find.byKey(trailingKey), findsOneWidget);
}
double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx;
double top(String text) => tester.getTopLeft(find.text(text)).dy;
double bottom(String text) => tester.getBottomLeft(find.text(text)).dy;
double width(String text) => tester.getSize(find.text(text)).width;
double height(String text) => tester.getSize(find.text(text)).height;
double leftKey(Key key) => tester.getTopLeft(find.byKey(key)).dx;
double rightKey(Key key) => tester.getTopRight(find.byKey(key)).dx;
double widthKey(Key key) => tester.getSize(find.byKey(key)).width;
double heightKey(Key key) => tester.getSize(find.byKey(key)).height;
// 16.0 padding to the left and right of the leading and trailing widgets
void testHorizontalGeometry() {
expect(left('leading'), 16.0);
expect(leftKey(leadingKey), 16.0);
expect(left('title'), 72.0);
if (hasSubtitle)
expect(left('subtitle'), 72.0);
expect(left('title'), right('leading') + 16.0);
expect(right('trailing'), 800.0 - 16.0);
expect(width('trailing'), 112.0);
expect(left('title'), rightKey(leadingKey) + 32.0);
expect(rightKey(trailingKey), 800.0 - 16.0);
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'), bottom('title'));
expect(height('trailing'), 14.0); // Fits on one line (doesn't wrap)
expect(heightKey(trailingKey), 24.0);
}
await tester.pumpWidget(buildFrame());
......@@ -127,8 +135,39 @@ void main() {
testChildren();
testHorizontalGeometry();
testVerticalGeometry(76.0);
await tester.pumpWidget(buildFrame(textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(64.0);
await tester.pumpWidget(buildFrame(dense: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(64.0);
await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(120.0);
await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(120.0);
await tester.pumpWidget(buildFrame(isThreeLine: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(120.0);
await tester.pumpWidget(buildFrame(isThreeLine: true, dense: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(120.0);
});
testWidgets('ListTile geometry (RTL)', (WidgetTester tester) async {
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.rtl,
......
......@@ -14,7 +14,8 @@ void main() {
decoration: new BoxDecoration(
color: const Color(0xFF00FF00),
gradient: new RadialGradient(
center: Alignment.topLeft, radius: 1.8,
center: Alignment.topLeft,
radius: 1.8,
colors: <Color>[Colors.yellow[500], Colors.blue[500]],
),
boxShadow: kElevationToShadow[3],
......@@ -71,14 +72,17 @@ void main() {
);
expect(coloredBox, hasAGoodToStringDeep);
expect(coloredBox.toStringDeep(minLevel: DiagnosticLevel.info), equalsIgnoringHashCodes(
'RenderDecoratedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' decoration: BoxDecoration:\n'
' <no decorations specified>\n'
' configuration: ImageConfiguration()\n'));
expect(
coloredBox.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes(
'RenderDecoratedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' decoration: BoxDecoration:\n'
' <no decorations specified>\n'
' configuration: ImageConfiguration()\n'),
);
final RenderBox paddingBox = new RenderPadding(
padding: const EdgeInsets.all(10.0),
......@@ -202,4 +206,52 @@ void main() {
' textDirection: ltr\n'),
);
});
test('honors constrainedAxis=Axis.horizontal', () {
final RenderConstrainedBox flexible =
new RenderConstrainedBox(additionalConstraints: const BoxConstraints.expand(height: 200.0));
final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox(
constrainedAxis: Axis.horizontal,
textDirection: TextDirection.ltr,
child: new RenderFlex(
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: <RenderBox>[flexible],
),
alignment: Alignment.center,
);
final FlexParentData flexParentData = flexible.parentData;
flexParentData.flex = 1;
flexParentData.fit = FlexFit.tight;
final BoxConstraints viewport = const BoxConstraints(maxWidth: 100.0);
layout(unconstrained, constraints: viewport);
expect(unconstrained.size.width, equals(100.0), reason: 'constrained width');
expect(unconstrained.size.height, equals(200.0), reason: 'unconstrained height');
});
test('honors constrainedAxis=Axis.vertical', () {
final RenderConstrainedBox flexible =
new RenderConstrainedBox(additionalConstraints: const BoxConstraints.expand(width: 200.0));
final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox(
constrainedAxis: Axis.vertical,
textDirection: TextDirection.ltr,
child: new RenderFlex(
direction: Axis.vertical,
textDirection: TextDirection.ltr,
children: <RenderBox>[flexible],
),
alignment: Alignment.center,
);
final FlexParentData flexParentData = flexible.parentData;
flexParentData.flex = 1;
flexParentData.fit = FlexFit.tight;
final BoxConstraints viewport = const BoxConstraints(maxHeight: 100.0);
layout(unconstrained, constraints: viewport);
expect(unconstrained.size.width, equals(200.0), reason: 'unconstrained width');
expect(unconstrained.size.height, equals(100.0), reason: 'constrained height');
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment