Unverified Commit a35aa3fd authored by Hans Muller's avatar Hans Muller Committed by GitHub

Add new ListTile parameters to ListTileTheme (#69982)

parent e148bf87
...@@ -53,6 +53,9 @@ class ListTileTheme extends InheritedTheme { ...@@ -53,6 +53,9 @@ class ListTileTheme extends InheritedTheme {
this.tileColor, this.tileColor,
this.selectedTileColor, this.selectedTileColor,
this.enableFeedback, this.enableFeedback,
this.horizontalTitleGap,
this.minVerticalPadding,
this.minLeadingWidth,
required Widget child, required Widget child,
}) : super(key: key, child: child); }) : super(key: key, child: child);
...@@ -72,6 +75,9 @@ class ListTileTheme extends InheritedTheme { ...@@ -72,6 +75,9 @@ class ListTileTheme extends InheritedTheme {
Color? tileColor, Color? tileColor,
Color? selectedTileColor, Color? selectedTileColor,
bool? enableFeedback, bool? enableFeedback,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
required Widget child, required Widget child,
}) { }) {
assert(child != null); assert(child != null);
...@@ -90,6 +96,9 @@ class ListTileTheme extends InheritedTheme { ...@@ -90,6 +96,9 @@ class ListTileTheme extends InheritedTheme {
tileColor: tileColor ?? parent.tileColor, tileColor: tileColor ?? parent.tileColor,
selectedTileColor: selectedTileColor ?? parent.selectedTileColor, selectedTileColor: selectedTileColor ?? parent.selectedTileColor,
enableFeedback: enableFeedback ?? parent.enableFeedback, enableFeedback: enableFeedback ?? parent.enableFeedback,
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
child: child, child: child,
); );
}, },
...@@ -134,6 +143,21 @@ class ListTileTheme extends InheritedTheme { ...@@ -134,6 +143,21 @@ class ListTileTheme extends InheritedTheme {
/// If [ListTile.selectedTileColor] is provided, [selectedTileColor] is ignored. /// If [ListTile.selectedTileColor] is provided, [selectedTileColor] is ignored.
final Color? selectedTileColor; final Color? selectedTileColor;
/// The horizontal gap between the titles and the leading/trailing widgets.
///
/// If specified, overrides the default value of [ListTile.horizontalTitleGap].
final double? horizontalTitleGap;
/// The minimum padding on the top and bottom of the title and subtitle widgets.
///
/// If specified, overrides the default value of [ListTile.minVerticalPadding].
final double? minVerticalPadding;
/// The minimum width allocated for the [ListTile.leading] widget.
///
/// If specified, overrides the default value of [ListTile.minLeadingWidth].
final double? minLeadingWidth;
/// If specified, defines the feedback property for `ListTile`. /// If specified, defines the feedback property for `ListTile`.
/// ///
/// If [ListTile.enableFeedback] is provided, [enableFeedback] is ignored. /// If [ListTile.enableFeedback] is provided, [enableFeedback] is ignored.
...@@ -164,6 +188,9 @@ class ListTileTheme extends InheritedTheme { ...@@ -164,6 +188,9 @@ class ListTileTheme extends InheritedTheme {
tileColor: tileColor, tileColor: tileColor,
selectedTileColor: selectedTileColor, selectedTileColor: selectedTileColor,
enableFeedback: enableFeedback, enableFeedback: enableFeedback,
horizontalTitleGap: horizontalTitleGap,
minVerticalPadding: minVerticalPadding,
minLeadingWidth: minLeadingWidth,
child: child, child: child,
); );
} }
...@@ -179,7 +206,10 @@ class ListTileTheme extends InheritedTheme { ...@@ -179,7 +206,10 @@ class ListTileTheme extends InheritedTheme {
|| contentPadding != oldWidget.contentPadding || contentPadding != oldWidget.contentPadding
|| tileColor != oldWidget.tileColor || tileColor != oldWidget.tileColor
|| selectedTileColor != oldWidget.selectedTileColor || selectedTileColor != oldWidget.selectedTileColor
|| enableFeedback != oldWidget.enableFeedback; || enableFeedback != oldWidget.enableFeedback
|| horizontalTitleGap != oldWidget.horizontalTitleGap
|| minVerticalPadding != oldWidget.minVerticalPadding
|| minLeadingWidth != oldWidget.minLeadingWidth;
} }
} }
...@@ -719,17 +749,14 @@ class ListTile extends StatelessWidget { ...@@ -719,17 +749,14 @@ class ListTile extends StatelessWidget {
this.tileColor, this.tileColor,
this.selectedTileColor, this.selectedTileColor,
this.enableFeedback, this.enableFeedback,
this.horizontalTitleGap = 16.0, this.horizontalTitleGap,
this.minVerticalPadding = 4.0, this.minVerticalPadding,
this.minLeadingWidth = 40.0, this.minLeadingWidth,
}) : assert(isThreeLine != null), }) : assert(isThreeLine != null),
assert(enabled != null), assert(enabled != null),
assert(selected != null), assert(selected != null),
assert(autofocus != null), assert(autofocus != null),
assert(!isThreeLine || subtitle != null), assert(!isThreeLine || subtitle != null),
assert(horizontalTitleGap != null),
assert(minVerticalPadding != null),
assert(minLeadingWidth != null),
super(key: key); super(key: key);
/// A widget to display before the title. /// A widget to display before the title.
...@@ -929,13 +956,22 @@ class ListTile extends StatelessWidget { ...@@ -929,13 +956,22 @@ class ListTile extends StatelessWidget {
final bool? enableFeedback; final bool? enableFeedback;
/// The horizontal gap between the titles and the leading/trailing widgets. /// The horizontal gap between the titles and the leading/trailing widgets.
final double horizontalTitleGap; ///
/// If null, then the value of [ListTileTheme.horizontalTitleGap] is used. If
/// that is also null, then a default value of 16 is used.
final double? horizontalTitleGap;
/// The minimum padding on the top and bottom of the title and subtitle widgets. /// The minimum padding on the top and bottom of the title and subtitle widgets.
final double minVerticalPadding; ///
/// If null, then the value of [ListTileTheme.minVerticalPadding] is used. If
/// that is also null, then a default value of 4 is used.
final double? minVerticalPadding;
/// The minimum leading width. /// The minimum width allocated for the [ListTile.leading] widget.
final double minLeadingWidth; ///
/// If null, then the value of [ListTileTheme.minLeadingWidth] is used. If
/// that is also null, then a default value of 40 is used.
final double? minLeadingWidth;
/// Add a one pixel border in between each tile. If color isn't specified the /// Add a one pixel border in between each tile. If color isn't specified the
/// [ThemeData.dividerColor] of the context's [Theme] is used. /// [ThemeData.dividerColor] of the context's [Theme] is used.
...@@ -1104,12 +1140,11 @@ class ListTile extends StatelessWidget { ...@@ -1104,12 +1140,11 @@ class ListTile extends StatelessWidget {
const EdgeInsets _defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0); const EdgeInsets _defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0);
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
final bool resolvedEnableFeedback = enableFeedback ?? tileTheme.enableFeedback ?? true;
final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection) final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
?? tileTheme.contentPadding?.resolve(textDirection) ?? tileTheme.contentPadding?.resolve(textDirection)
?? _defaultContentPadding; ?? _defaultContentPadding;
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>( final MouseCursor resolvedMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
mouseCursor ?? MaterialStateMouseCursor.clickable, mouseCursor ?? MaterialStateMouseCursor.clickable,
<MaterialState>{ <MaterialState>{
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled, if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
...@@ -1121,13 +1156,13 @@ class ListTile extends StatelessWidget { ...@@ -1121,13 +1156,13 @@ class ListTile extends StatelessWidget {
customBorder: shape ?? tileTheme.shape, customBorder: shape ?? tileTheme.shape,
onTap: enabled ? onTap : null, onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null, onLongPress: enabled ? onLongPress : null,
mouseCursor: effectiveMouseCursor, mouseCursor: resolvedMouseCursor,
canRequestFocus: enabled, canRequestFocus: enabled,
focusNode: focusNode, focusNode: focusNode,
focusColor: focusColor, focusColor: focusColor,
hoverColor: hoverColor, hoverColor: hoverColor,
autofocus: autofocus, autofocus: autofocus,
enableFeedback: resolvedEnableFeedback, enableFeedback: enableFeedback ?? tileTheme.enableFeedback ?? true,
child: Semantics( child: Semantics(
selected: selected, selected: selected,
enabled: enabled, enabled: enabled,
...@@ -1148,9 +1183,9 @@ class ListTile extends StatelessWidget { ...@@ -1148,9 +1183,9 @@ class ListTile extends StatelessWidget {
textDirection: textDirection, textDirection: textDirection,
titleBaselineType: titleStyle.textBaseline!, titleBaselineType: titleStyle.textBaseline!,
subtitleBaselineType: subtitleStyle?.textBaseline, subtitleBaselineType: subtitleStyle?.textBaseline,
horizontalTitleGap: horizontalTitleGap, horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
minVerticalPadding: minVerticalPadding, minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? 4,
minLeadingWidth: minLeadingWidth, minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? 40,
), ),
), ),
), ),
......
...@@ -1836,7 +1836,7 @@ void main() { ...@@ -1836,7 +1836,7 @@ void main() {
}); });
testWidgets('ListTile horizontalTitleGap = 0.0', (WidgetTester tester) async { testWidgets('ListTile horizontalTitleGap = 0.0', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection) { Widget buildFrame(TextDirection textDirection, { double? themeHorizontalTitleGap, double? widgetHorizontalTitleGap }) {
return MediaQuery( return MediaQuery(
data: const MediaQueryData( data: const MediaQueryData(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
...@@ -1845,13 +1845,16 @@ void main() { ...@@ -1845,13 +1845,16 @@ void main() {
child: Directionality( child: Directionality(
textDirection: textDirection, textDirection: textDirection,
child: Material( child: Material(
child: ListTileTheme(
horizontalTitleGap: themeHorizontalTitleGap,
child: Container( child: Container(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: const ListTile( child: ListTile(
horizontalTitleGap: 0.0, horizontalTitleGap: widgetHorizontalTitleGap,
leading: Text('L'), leading: const Text('L'),
title: Text('title'), title: const Text('title'),
trailing: Text('T'), trailing: const Text('T'),
),
), ),
), ),
), ),
...@@ -1862,15 +1865,29 @@ void main() { ...@@ -1862,15 +1865,29 @@ void main() {
double left(String text) => tester.getTopLeft(find.text(text)).dx; double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx; double right(String text) => tester.getTopRight(find.text(text)).dx;
await tester.pumpWidget(buildFrame(TextDirection.ltr)); await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetHorizontalTitleGap: 0));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(left('title'), 56.0);
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 0));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0)); expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(left('title'), 56.0); // horizontalTitleGap: 0 expect(left('title'), 56.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl)); await tester.pumpWidget(buildFrame(TextDirection.ltr, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(left('title'), 56.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetHorizontalTitleGap: 0));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0)); expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(right('title'), 744.0); // horizontalTitleGap: 0 expect(right('title'), 744.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 0));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(right('title'), 744.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeHorizontalTitleGap: 10, widgetHorizontalTitleGap: 0));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(right('title'), 744.0);
}); });
testWidgets('ListTile horizontalTitleGap = (default) && ListTile minLeadingWidth = (default)', (WidgetTester tester) async { testWidgets('ListTile horizontalTitleGap = (default) && ListTile minLeadingWidth = (default)', (WidgetTester tester) async {
...@@ -1913,7 +1930,7 @@ void main() { ...@@ -1913,7 +1930,7 @@ void main() {
}); });
testWidgets('ListTile minVerticalPadding = 80.0', (WidgetTester tester) async { testWidgets('ListTile minVerticalPadding = 80.0', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection) { Widget buildFrame(TextDirection textDirection, { double? themeMinVerticalPadding, double? widgetMinVerticalPadding }) {
return MediaQuery( return MediaQuery(
data: const MediaQueryData( data: const MediaQueryData(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
...@@ -1922,13 +1939,16 @@ void main() { ...@@ -1922,13 +1939,16 @@ void main() {
child: Directionality( child: Directionality(
textDirection: textDirection, textDirection: textDirection,
child: Material( child: Material(
child: ListTileTheme(
minVerticalPadding: themeMinVerticalPadding,
child: Container( child: Container(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: const ListTile( child: ListTile(
minVerticalPadding: 80.0, minVerticalPadding: widgetMinVerticalPadding,
leading: Text('L'), leading: const Text('L'),
title: Text('title'), title: const Text('title'),
trailing: Text('T'), trailing: const Text('T'),
),
), ),
), ),
), ),
...@@ -1937,21 +1957,29 @@ void main() { ...@@ -1937,21 +1957,29 @@ void main() {
} }
await tester.pumpWidget(buildFrame(TextDirection.ltr)); await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinVerticalPadding: 80));
// minVerticalPadding: 80.0
// 80 + 80 + 16(Title) = 176 // 80 + 80 + 16(Title) = 176
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0)); expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
await tester.pumpWidget(buildFrame(TextDirection.rtl)); await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 80));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
// minVerticalPadding: 80.0 await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinVerticalPadding: 80));
// 80 + 80 + 16(Title) = 176 // 80 + 80 + 16(Title) = 176
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0)); expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 80));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinVerticalPadding: 0, widgetMinVerticalPadding: 80));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 176.0));
}); });
testWidgets('ListTile minLeadingWidth = 60.0', (WidgetTester tester) async { testWidgets('ListTile minLeadingWidth = 60.0', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection) { Widget buildFrame(TextDirection textDirection, { double? themeMinLeadingWidth, double? widgetMinLeadingWidth }) {
return MediaQuery( return MediaQuery(
data: const MediaQueryData( data: const MediaQueryData(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
...@@ -1960,13 +1988,16 @@ void main() { ...@@ -1960,13 +1988,16 @@ void main() {
child: Directionality( child: Directionality(
textDirection: textDirection, textDirection: textDirection,
child: Material( child: Material(
child: ListTileTheme(
minLeadingWidth: themeMinLeadingWidth,
child: Container( child: Container(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: const ListTile( child: ListTile(
minLeadingWidth: 60.0, minLeadingWidth: widgetMinLeadingWidth,
leading: Text('L'), leading: const Text('L'),
title: Text('title'), title: const Text('title'),
trailing: Text('T'), trailing: const Text('T'),
),
), ),
), ),
), ),
...@@ -1977,18 +2008,31 @@ void main() { ...@@ -1977,18 +2008,31 @@ void main() {
double left(String text) => tester.getTopLeft(find.text(text)).dx; double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx; double right(String text) => tester.getTopRight(find.text(text)).dx;
await tester.pumpWidget(buildFrame(TextDirection.ltr)); await tester.pumpWidget(buildFrame(TextDirection.ltr, widgetMinLeadingWidth: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0)); expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
// minLeadingWidth: 60.0
// 92.0 = 16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0 // 92.0 = 16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0
expect(left('title'), 92.0); expect(left('title'), 92.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl)); await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinLeadingWidth: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(left('title'), 92.0);
await tester.pumpWidget(buildFrame(TextDirection.ltr, themeMinLeadingWidth: 0, widgetMinLeadingWidth: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(left('title'), 92.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl, widgetMinLeadingWidth: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0)); expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
// minLeadingWidth: 60.0
// 708.0 = 800.0 - (16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0) // 708.0 = 800.0 - (16.0(Default contentPadding) + 16.0(Default horizontalTitleGap) + 60.0)
expect(right('title'), 708.0); expect(right('title'), 708.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinLeadingWidth: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(right('title'), 708.0);
await tester.pumpWidget(buildFrame(TextDirection.rtl, themeMinLeadingWidth: 0, widgetMinLeadingWidth: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(right('title'), 708.0);
}); });
} }
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