diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index a32a2b7c04..931d2b2070 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -988,26 +988,20 @@ class _RenderListTile extends RenderBox { // This attempts to implement the redlines for the vertical position of the // leading and trailing icons on the spec page: // https://material.io/design/components/lists.html#specs - // Some liberties have been taken to handle cases that aren't covered by - // that specification, such as leading and trailing widgets with weird - // sizes, "one-line" list tiles with title widgets that span multiple lines, - // etc. + // The interpretation for these red lines is as follows: + // - For large tiles (> 72dp), both leading and trailing controls should be + // a fixed distance from top. As per guidelines this is set to 16dp. + // - For smaller tiles, trailing should always be centered. Leading can be + // centered or closer to the top. It should never be further than 16dp + // to the top. double leadingY; double trailingY; - if (isOneLine) { - leadingY = (defaultTileHeight - leadingSize.height) / 2.0; - trailingY = (defaultTileHeight - trailingSize.height) / 2.0; - } else if (isTwoLine) { - if (isDense) { - leadingY = 12.0; // by extrapolation - trailingY = 20.0; // by extrapolation - } else { - leadingY = leadingSize.height <= 40.0 ? 16.0 : 8.0; - trailingY = 24.0; - } - } else { + if (tileHeight > 72.0) { leadingY = 16.0; trailingY = 16.0; + } else { + leadingY = math.min((tileHeight - leadingSize.height) / 2.0, 16.0); + trailingY = (tileHeight - trailingSize.height) / 2.0; } switch (textDirection) { diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index 652e4d1ad7..814c7ccb2f 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -585,11 +585,11 @@ void main() { ), ), ); - // LEFT TOP WIDTH HEIGHT + // LEFT TOP WIDTH HEIGHT expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 177.0)); - expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 4.0, 40.0, 40.0)); - expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 12.0, 24.0, 24.0)); - expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 177.0, 800.0, 48.0)); + expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0)); + expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0)); + expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 177.0, 800.0, 48.0)); expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 177.0 + 4.0, 40.0, 40.0)); expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 177.0 + 12.0, 24.0, 24.0)); @@ -615,11 +615,11 @@ void main() { ), ); await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense - // LEFT TOP WIDTH HEIGHT + // LEFT TOP WIDTH HEIGHT expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 216.0)); - expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 8.0, 40.0, 40.0)); + expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0)); expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0)); - expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 216.0, 800.0, 56.0)); + expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 216.0 , 800.0, 56.0)); expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 216.0 + 8.0, 40.0, 40.0)); expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0, 24.0, 24.0)); @@ -650,8 +650,8 @@ void main() { ); // LEFT TOP WIDTH HEIGHT expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 180.0)); - expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 12.0, 40.0, 40.0)); - expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 20.0, 24.0, 24.0)); + expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0)); + expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0)); expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 64.0)); expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 12.0, 40.0, 40.0)); expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 20.0, 24.0, 24.0)); @@ -682,7 +682,7 @@ void main() { // LEFT TOP WIDTH HEIGHT expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 180.0)); expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0)); - expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 24.0, 24.0, 24.0)); + expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0)); expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 72.0)); expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 16.0, 40.0, 40.0)); expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 24.0, 24.0, 24.0)); @@ -754,5 +754,35 @@ void main() { expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 88.0)); expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 16.0, 40.0, 40.0)); expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 16.0, 24.0, 24.0)); + + // "ONE-LINE" with Small Leading Widget + await tester.pumpWidget( + MaterialApp( + home: Material( + child: ListView( + children: const [ + ListTile( + leading: SizedBox(height:12.0, width:24.0, child: Placeholder()), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + ), + ListTile( + leading: SizedBox(height:12.0, width:24.0, child: Placeholder()), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + ), + ], + ), + ), + ), + ); + await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense + // LEFT TOP WIDTH HEIGHT + expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 216.0)); + expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH( 16.0, 16.0, 24.0, 12.0)); + expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0)); + expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 216.0 , 800.0, 56.0)); + expect(tester.getRect(find.byType(Placeholder).at(2)), Rect.fromLTWH( 16.0, 216.0 + 16.0, 24.0, 12.0)); + expect(tester.getRect(find.byType(Placeholder).at(3)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0, 24.0, 24.0)); }); }