Skip to content

Commit a35aa3f

Browse files
authored
Add new ListTile parameters to ListTileTheme (flutter#69982)
1 parent e148bf8 commit a35aa3f

File tree

2 files changed

+135
-56
lines changed

2 files changed

+135
-56
lines changed

packages/flutter/lib/src/material/list_tile.dart

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ class ListTileTheme extends InheritedTheme {
5353
this.tileColor,
5454
this.selectedTileColor,
5555
this.enableFeedback,
56+
this.horizontalTitleGap,
57+
this.minVerticalPadding,
58+
this.minLeadingWidth,
5659
required Widget child,
5760
}) : super(key: key, child: child);
5861

@@ -72,6 +75,9 @@ class ListTileTheme extends InheritedTheme {
7275
Color? tileColor,
7376
Color? selectedTileColor,
7477
bool? enableFeedback,
78+
double? horizontalTitleGap,
79+
double? minVerticalPadding,
80+
double? minLeadingWidth,
7581
required Widget child,
7682
}) {
7783
assert(child != null);
@@ -90,6 +96,9 @@ class ListTileTheme extends InheritedTheme {
9096
tileColor: tileColor ?? parent.tileColor,
9197
selectedTileColor: selectedTileColor ?? parent.selectedTileColor,
9298
enableFeedback: enableFeedback ?? parent.enableFeedback,
99+
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
100+
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
101+
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
93102
child: child,
94103
);
95104
},
@@ -134,6 +143,21 @@ class ListTileTheme extends InheritedTheme {
134143
/// If [ListTile.selectedTileColor] is provided, [selectedTileColor] is ignored.
135144
final Color? selectedTileColor;
136145

146+
/// The horizontal gap between the titles and the leading/trailing widgets.
147+
///
148+
/// If specified, overrides the default value of [ListTile.horizontalTitleGap].
149+
final double? horizontalTitleGap;
150+
151+
/// The minimum padding on the top and bottom of the title and subtitle widgets.
152+
///
153+
/// If specified, overrides the default value of [ListTile.minVerticalPadding].
154+
final double? minVerticalPadding;
155+
156+
/// The minimum width allocated for the [ListTile.leading] widget.
157+
///
158+
/// If specified, overrides the default value of [ListTile.minLeadingWidth].
159+
final double? minLeadingWidth;
160+
137161
/// If specified, defines the feedback property for `ListTile`.
138162
///
139163
/// If [ListTile.enableFeedback] is provided, [enableFeedback] is ignored.
@@ -164,6 +188,9 @@ class ListTileTheme extends InheritedTheme {
164188
tileColor: tileColor,
165189
selectedTileColor: selectedTileColor,
166190
enableFeedback: enableFeedback,
191+
horizontalTitleGap: horizontalTitleGap,
192+
minVerticalPadding: minVerticalPadding,
193+
minLeadingWidth: minLeadingWidth,
167194
child: child,
168195
);
169196
}
@@ -179,7 +206,10 @@ class ListTileTheme extends InheritedTheme {
179206
|| contentPadding != oldWidget.contentPadding
180207
|| tileColor != oldWidget.tileColor
181208
|| selectedTileColor != oldWidget.selectedTileColor
182-
|| enableFeedback != oldWidget.enableFeedback;
209+
|| enableFeedback != oldWidget.enableFeedback
210+
|| horizontalTitleGap != oldWidget.horizontalTitleGap
211+
|| minVerticalPadding != oldWidget.minVerticalPadding
212+
|| minLeadingWidth != oldWidget.minLeadingWidth;
183213
}
184214
}
185215

@@ -719,17 +749,14 @@ class ListTile extends StatelessWidget {
719749
this.tileColor,
720750
this.selectedTileColor,
721751
this.enableFeedback,
722-
this.horizontalTitleGap = 16.0,
723-
this.minVerticalPadding = 4.0,
724-
this.minLeadingWidth = 40.0,
752+
this.horizontalTitleGap,
753+
this.minVerticalPadding,
754+
this.minLeadingWidth,
725755
}) : assert(isThreeLine != null),
726756
assert(enabled != null),
727757
assert(selected != null),
728758
assert(autofocus != null),
729759
assert(!isThreeLine || subtitle != null),
730-
assert(horizontalTitleGap != null),
731-
assert(minVerticalPadding != null),
732-
assert(minLeadingWidth != null),
733760
super(key: key);
734761

735762
/// A widget to display before the title.
@@ -929,13 +956,22 @@ class ListTile extends StatelessWidget {
929956
final bool? enableFeedback;
930957

931958
/// The horizontal gap between the titles and the leading/trailing widgets.
932-
final double horizontalTitleGap;
959+
///
960+
/// If null, then the value of [ListTileTheme.horizontalTitleGap] is used. If
961+
/// that is also null, then a default value of 16 is used.
962+
final double? horizontalTitleGap;
933963

934964
/// The minimum padding on the top and bottom of the title and subtitle widgets.
935-
final double minVerticalPadding;
965+
///
966+
/// If null, then the value of [ListTileTheme.minVerticalPadding] is used. If
967+
/// that is also null, then a default value of 4 is used.
968+
final double? minVerticalPadding;
936969

937-
/// The minimum leading width.
938-
final double minLeadingWidth;
970+
/// The minimum width allocated for the [ListTile.leading] widget.
971+
///
972+
/// If null, then the value of [ListTileTheme.minLeadingWidth] is used. If
973+
/// that is also null, then a default value of 40 is used.
974+
final double? minLeadingWidth;
939975

940976
/// Add a one pixel border in between each tile. If color isn't specified the
941977
/// [ThemeData.dividerColor] of the context's [Theme] is used.
@@ -1104,12 +1140,11 @@ class ListTile extends StatelessWidget {
11041140

11051141
const EdgeInsets _defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0);
11061142
final TextDirection textDirection = Directionality.of(context);
1107-
final bool resolvedEnableFeedback = enableFeedback ?? tileTheme.enableFeedback ?? true;
11081143
final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
11091144
?? tileTheme.contentPadding?.resolve(textDirection)
11101145
?? _defaultContentPadding;
11111146

1112-
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
1147+
final MouseCursor resolvedMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
11131148
mouseCursor ?? MaterialStateMouseCursor.clickable,
11141149
<MaterialState>{
11151150
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
@@ -1121,13 +1156,13 @@ class ListTile extends StatelessWidget {
11211156
customBorder: shape ?? tileTheme.shape,
11221157
onTap: enabled ? onTap : null,
11231158
onLongPress: enabled ? onLongPress : null,
1124-
mouseCursor: effectiveMouseCursor,
1159+
mouseCursor: resolvedMouseCursor,
11251160
canRequestFocus: enabled,
11261161
focusNode: focusNode,
11271162
focusColor: focusColor,
11281163
hoverColor: hoverColor,
11291164
autofocus: autofocus,
1130-
enableFeedback: resolvedEnableFeedback,
1165+
enableFeedback: enableFeedback ?? tileTheme.enableFeedback ?? true,
11311166
child: Semantics(
11321167
selected: selected,
11331168
enabled: enabled,
@@ -1148,9 +1183,9 @@ class ListTile extends StatelessWidget {
11481183
textDirection: textDirection,
11491184
titleBaselineType: titleStyle.textBaseline!,
11501185
subtitleBaselineType: subtitleStyle?.textBaseline,
1151-
horizontalTitleGap: horizontalTitleGap,
1152-
minVerticalPadding: minVerticalPadding,
1153-
minLeadingWidth: minLeadingWidth,
1186+
horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
1187+
minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? 4,
1188+
minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? 40,
11541189
),
11551190
),
11561191
),

packages/flutter/test/material/list_tile_test.dart

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,7 +1836,7 @@ void main() {
18361836
});
18371837

18381838
testWidgets('ListTile horizontalTitleGap = 0.0', (WidgetTester tester) async {
1839-
Widget buildFrame(TextDirection textDirection) {
1839+
Widget buildFrame(TextDirection textDirection, { double? themeHorizontalTitleGap, double? widgetHorizontalTitleGap }) {
18401840
return MediaQuery(
18411841
data: const MediaQueryData(
18421842
padding: EdgeInsets.zero,
@@ -1845,13 +1845,16 @@ void main() {
18451845
child: Directionality(
18461846
textDirection: textDirection,
18471847
child: Material(
1848-
child: Container(
1849-
alignment: Alignment.topLeft,
1850-
child: const ListTile(
1851-
horizontalTitleGap: 0.0,
1852-
leading: Text('L'),
1853-
title: Text('title'),
1854-
trailing: Text('T'),
1848+
child: ListTileTheme(
1849+
horizontalTitleGap: themeHorizontalTitleGap,
1850+
child: Container(
1851+
alignment: Alignment.topLeft,
1852+
child: ListTile(
1853+
horizontalTitleGap: widgetHorizontalTitleGap,
1854+
leading: const Text('L'),
1855+
title: const Text('title'),
1856+
trailing: const Text('T'),
1857+
),
18551858
),
18561859
),
18571860
),
@@ -1862,15 +1865,29 @@ void main() {
18621865
double left(String text) => tester.getTopLeft(find.text(text)).dx;
18631866
double right(String text) => tester.getTopRight(find.text(text)).dx;
18641867

1865-
await tester.pumpWidget(buildFrame(TextDirection.ltr));
1868+
await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetHorizontalTitleGap: 0));
1869+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1870+
expect(left('title'), 56.0);
18661871

1872+
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 0));
18671873
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1868-
expect(left('title'), 56.0); // horizontalTitleGap: 0
1874+
expect(left('title'), 56.0);
18691875

1870-
await tester.pumpWidget(buildFrame(TextDirection.rtl));
1876+
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
1877+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1878+
expect(left('title'), 56.0);
18711879

1880+
await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetHorizontalTitleGap: 0));
18721881
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1873-
expect(right('title'), 744.0); // horizontalTitleGap: 0
1882+
expect(right('title'), 744.0);
1883+
1884+
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 0));
1885+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1886+
expect(right('title'), 744.0);
1887+
1888+
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
1889+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1890+
expect(right('title'), 744.0);
18741891
});
18751892

18761893
testWidgets('ListTile horizontalTitleGap = (default) && ListTile minLeadingWidth = (default)', (WidgetTester tester) async {
@@ -1913,7 +1930,7 @@ void main() {
19131930
});
19141931

19151932
testWidgets('ListTile minVerticalPadding = 80.0', (WidgetTester tester) async {
1916-
Widget buildFrame(TextDirection textDirection) {
1933+
Widget buildFrame(TextDirection textDirection, { double? themeMinVerticalPadding, double? widgetMinVerticalPadding }) {
19171934
return MediaQuery(
19181935
data: const MediaQueryData(
19191936
padding: EdgeInsets.zero,
@@ -1922,13 +1939,16 @@ void main() {
19221939
child: Directionality(
19231940
textDirection: textDirection,
19241941
child: Material(
1925-
child: Container(
1926-
alignment: Alignment.topLeft,
1927-
child: const ListTile(
1928-
minVerticalPadding: 80.0,
1929-
leading: Text('L'),
1930-
title: Text('title'),
1931-
trailing: Text('T'),
1942+
child: ListTileTheme(
1943+
minVerticalPadding: themeMinVerticalPadding,
1944+
child: Container(
1945+
alignment: Alignment.topLeft,
1946+
child: ListTile(
1947+
minVerticalPadding: widgetMinVerticalPadding,
1948+
leading: const Text('L'),
1949+
title: const Text('title'),
1950+
trailing: const Text('T'),
1951+
),
19321952
),
19331953
),
19341954
),
@@ -1937,21 +1957,29 @@ void main() {
19371957
}
19381958

19391959

1940-
await tester.pumpWidget(buildFrame(TextDirection.ltr));
1941-
1942-
// minVerticalPadding: 80.0
1960+
await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinVerticalPadding: 80));
19431961
// 80 + 80 + 16(Title) = 176
19441962
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
19451963

1946-
await tester.pumpWidget(buildFrame(TextDirection.rtl));
1964+
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 80));
1965+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
19471966

1948-
// minVerticalPadding: 80.0
1967+
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
1968+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
1969+
1970+
await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinVerticalPadding: 80));
19491971
// 80 + 80 + 16(Title) = 176
19501972
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
1973+
1974+
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 80));
1975+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
1976+
1977+
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
1978+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
19511979
});
19521980

19531981
testWidgets('ListTile minLeadingWidth = 60.0', (WidgetTester tester) async {
1954-
Widget buildFrame(TextDirection textDirection) {
1982+
Widget buildFrame(TextDirection textDirection, { double? themeMinLeadingWidth, double? widgetMinLeadingWidth }) {
19551983
return MediaQuery(
19561984
data: const MediaQueryData(
19571985
padding: EdgeInsets.zero,
@@ -1960,13 +1988,16 @@ void main() {
19601988
child: Directionality(
19611989
textDirection: textDirection,
19621990
child: Material(
1963-
child: Container(
1964-
alignment: Alignment.topLeft,
1965-
child: const ListTile(
1966-
minLeadingWidth: 60.0,
1967-
leading: Text('L'),
1968-
title: Text('title'),
1969-
trailing: Text('T'),
1991+
child: ListTileTheme(
1992+
minLeadingWidth: themeMinLeadingWidth,
1993+
child: Container(
1994+
alignment: Alignment.topLeft,
1995+
child: ListTile(
1996+
minLeadingWidth: widgetMinLeadingWidth,
1997+
leading: const Text('L'),
1998+
title: const Text('title'),
1999+
trailing: const Text('T'),
2000+
),
19702001
),
19712002
),
19722003
),
@@ -1977,18 +2008,31 @@ void main() {
19772008
double left(String text) => tester.getTopLeft(find.text(text)).dx;
19782009
double right(String text) => tester.getTopRight(find.text(text)).dx;
19792010

1980-
await tester.pumpWidget(buildFrame(TextDirection.ltr));
1981-
2011+
await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinLeadingWidth: 60));
19822012
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1983-
// minLeadingWidth: 60.0
19842013
// 92.0 = 16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0
19852014
expect(left('title'), 92.0);
19862015

1987-
await tester.pumpWidget(buildFrame(TextDirection.rtl));
2016+
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinLeadingWidth: 60));
2017+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
2018+
expect(left('title'), 92.0);
2019+
2020+
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinLeadingWidth: 0, widgetMinLeadingWidth: 60));
2021+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
2022+
expect(left('title'), 92.0);
2023+
19882024

2025+
await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinLeadingWidth: 60));
19892026
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
1990-
// minLeadingWidth: 60.0
19912027
// 708.0 = 800.0 - (16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0)
19922028
expect(right('title'), 708.0);
2029+
2030+
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinLeadingWidth: 60));
2031+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
2032+
expect(right('title'), 708.0);
2033+
2034+
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinLeadingWidth: 0, widgetMinLeadingWidth: 60));
2035+
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
2036+
expect(right('title'), 708.0);
19932037
});
19942038
}

0 commit comments

Comments
 (0)