Add a minTileHeight to ListTile widget so its height can be customized to less than the default height. (#145244)

fixes: https://github.com/flutter/flutter/issues/145369
This commit is contained in:
hangyu 2024-03-19 10:58:16 -07:00 committed by GitHub
parent 6190c5eea1
commit 2b317d584f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 88 additions and 6 deletions

View file

@ -251,6 +251,7 @@ class ExpansionTile extends StatefulWidget {
this.controller,
this.dense,
this.visualDensity,
this.minTileHeight,
this.enableFeedback = true,
this.enabled = true,
this.expansionAnimationStyle,
@ -508,6 +509,9 @@ class ExpansionTile extends StatefulWidget {
/// {@macro flutter.material.themedata.visualDensity}
final VisualDensity? visualDensity;
/// {@macro flutter.material.ListTile.minTileHeight}
final double? minTileHeight;
/// {@macro flutter.material.ListTile.enableFeedback}
final bool? enableFeedback;
@ -710,6 +714,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
title: widget.title,
subtitle: widget.subtitle,
trailing: widget.trailing ?? _buildTrailingIcon(context),
minTileHeight: widget.minTileHeight,
),
),
),

View file

@ -364,6 +364,7 @@ class ListTile extends StatelessWidget {
this.horizontalTitleGap,
this.minVerticalPadding,
this.minLeadingWidth,
this.minTileHeight,
this.titleAlignment,
}) : assert(!isThreeLine || subtitle != null);
@ -669,6 +670,16 @@ class ListTile extends StatelessWidget {
/// that is also null, then a default value of 40 is used.
final double? minLeadingWidth;
/// {@template flutter.material.ListTile.minTileHeight}
/// The minimum height allocated for the [ListTile] widget.
///
/// If this is null, default tile heights are 56.0, 72.0, and 88.0 for one,
/// two, and three lines of text respectively. If `isDense` is true, these
/// defaults are changed to 48.0, 64.0, and 76.0. A visual density value or
/// a large title will also adjust the default tile heights.
/// {@endtemplate}
final double? minTileHeight;
/// Defines how [ListTile.leading] and [ListTile.trailing] are
/// vertically aligned relative to the [ListTile]'s titles
/// ([ListTile.title] and [ListTile.subtitle]).
@ -884,6 +895,7 @@ class ListTile extends StatelessWidget {
horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? defaults.minVerticalPadding!,
minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? defaults.minLeadingWidth!,
minTileHeight: minTileHeight ?? tileTheme.minTileHeight,
titleAlignment: effectiveTitleAlignment,
),
),
@ -978,6 +990,7 @@ class _ListTile extends SlottedMultiChildRenderObjectWidget<_ListTileSlot, Rende
required this.horizontalTitleGap,
required this.minVerticalPadding,
required this.minLeadingWidth,
this.minTileHeight,
this.subtitleBaselineType,
required this.titleAlignment,
});
@ -995,6 +1008,7 @@ class _ListTile extends SlottedMultiChildRenderObjectWidget<_ListTileSlot, Rende
final double horizontalTitleGap;
final double minVerticalPadding;
final double minLeadingWidth;
final double? minTileHeight;
final ListTileTitleAlignment titleAlignment;
@override
@ -1022,6 +1036,7 @@ class _ListTile extends SlottedMultiChildRenderObjectWidget<_ListTileSlot, Rende
horizontalTitleGap: horizontalTitleGap,
minVerticalPadding: minVerticalPadding,
minLeadingWidth: minLeadingWidth,
minTileHeight: minTileHeight,
titleAlignment: titleAlignment,
);
}
@ -1037,6 +1052,7 @@ class _ListTile extends SlottedMultiChildRenderObjectWidget<_ListTileSlot, Rende
..subtitleBaselineType = subtitleBaselineType
..horizontalTitleGap = horizontalTitleGap
..minLeadingWidth = minLeadingWidth
..minTileHeight = minTileHeight
..minVerticalPadding = minVerticalPadding
..titleAlignment = titleAlignment;
}
@ -1053,7 +1069,8 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
required double horizontalTitleGap,
required double minVerticalPadding,
required double minLeadingWidth,
required ListTileTitleAlignment titleAlignment,
double? minTileHeight,
required ListTileTitleAlignment titleAlignment
}) : _isDense = isDense,
_visualDensity = visualDensity,
_isThreeLine = isThreeLine,
@ -1063,6 +1080,7 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
_horizontalTitleGap = horizontalTitleGap,
_minVerticalPadding = minVerticalPadding,
_minLeadingWidth = minLeadingWidth,
_minTileHeight = minTileHeight,
_titleAlignment = titleAlignment;
RenderBox? get leading => childForSlot(_ListTileSlot.leading);
@ -1179,6 +1197,16 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
markNeedsLayout();
}
double? _minTileHeight;
double? get minTileHeight => _minTileHeight;
set minTileHeight(double? value) {
if (_minTileHeight == value) {
return;
}
_minTileHeight = value;
markNeedsLayout();
}
ListTileTitleAlignment get titleAlignment => _titleAlignment;
ListTileTitleAlignment _titleAlignment;
set titleAlignment(ListTileTitleAlignment value) {
@ -1238,7 +1266,7 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
@override
double computeMinIntrinsicHeight(double width) {
return math.max(
_defaultTileHeight,
minTileHeight ?? _defaultTileHeight,
title!.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0),
);
}
@ -1345,19 +1373,17 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
assert(isOneLine);
}
final double defaultTileHeight = _defaultTileHeight;
double tileHeight;
double titleY;
double? subtitleY;
if (!hasSubtitle) {
tileHeight = math.max(defaultTileHeight, titleSize.height + 2.0 * _minVerticalPadding);
tileHeight = math.max(minTileHeight ?? _defaultTileHeight, titleSize.height + 2.0 * _minVerticalPadding);
titleY = (tileHeight - titleSize.height) / 2.0;
} else {
assert(subtitleBaselineType != null);
titleY = titleBaseline! - _boxBaseline(title!, titleBaselineType)!;
subtitleY = subtitleBaseline! - _boxBaseline(subtitle!, subtitleBaselineType!)! + visualDensity.vertical * 2.0;
tileHeight = defaultTileHeight;
tileHeight = minTileHeight ?? _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

View file

@ -63,6 +63,7 @@ class ListTileThemeData with Diagnosticable {
this.enableFeedback,
this.mouseCursor,
this.visualDensity,
this.minTileHeight,
this.titleAlignment,
});
@ -111,6 +112,9 @@ class ListTileThemeData with Diagnosticable {
/// Overrides the default value of [ListTile.minLeadingWidth].
final double? minLeadingWidth;
/// Overrides the default value of [ListTile.minTileHeight].
final double? minTileHeight;
/// Overrides the default value of [ListTile.enableFeedback].
final bool? enableFeedback;
@ -141,6 +145,7 @@ class ListTileThemeData with Diagnosticable {
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
double? minTileHeight,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
bool? isThreeLine,
@ -163,6 +168,7 @@ class ListTileThemeData with Diagnosticable {
horizontalTitleGap: horizontalTitleGap ?? this.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? this.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? this.minLeadingWidth,
minTileHeight: minTileHeight ?? this.minTileHeight,
enableFeedback: enableFeedback ?? this.enableFeedback,
mouseCursor: mouseCursor ?? this.mouseCursor,
visualDensity: visualDensity ?? this.visualDensity,
@ -191,6 +197,7 @@ class ListTileThemeData with Diagnosticable {
horizontalTitleGap: lerpDouble(a?.horizontalTitleGap, b?.horizontalTitleGap, t),
minVerticalPadding: lerpDouble(a?.minVerticalPadding, b?.minVerticalPadding, t),
minLeadingWidth: lerpDouble(a?.minLeadingWidth, b?.minLeadingWidth, t),
minTileHeight: lerpDouble(a?.minTileHeight, b?.minTileHeight, t),
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
@ -215,6 +222,7 @@ class ListTileThemeData with Diagnosticable {
horizontalTitleGap,
minVerticalPadding,
minLeadingWidth,
minTileHeight,
enableFeedback,
mouseCursor,
visualDensity,
@ -245,6 +253,7 @@ class ListTileThemeData with Diagnosticable {
&& other.horizontalTitleGap == horizontalTitleGap
&& other.minVerticalPadding == minVerticalPadding
&& other.minLeadingWidth == minLeadingWidth
&& other.minTileHeight == minTileHeight
&& other.enableFeedback == enableFeedback
&& other.mouseCursor == mouseCursor
&& other.visualDensity == visualDensity
@ -269,6 +278,7 @@ class ListTileThemeData with Diagnosticable {
properties.add(DoubleProperty('horizontalTitleGap', horizontalTitleGap, defaultValue: null));
properties.add(DoubleProperty('minVerticalPadding', minVerticalPadding, defaultValue: null));
properties.add(DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null));
properties.add(DoubleProperty('minTileHeight', minTileHeight, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
@ -488,6 +498,7 @@ class ListTileTheme extends InheritedTheme {
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
double? minTileHeight,
ListTileTitleAlignment? titleAlignment,
MaterialStateProperty<MouseCursor?>? mouseCursor,
VisualDensity? visualDensity,
@ -515,6 +526,7 @@ class ListTileTheme extends InheritedTheme {
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
minTileHeight: minTileHeight ?? parent.minTileHeight,
titleAlignment: titleAlignment ?? parent.titleAlignment,
mouseCursor: mouseCursor ?? parent.mouseCursor,
visualDensity: visualDensity ?? parent.visualDensity,

View file

@ -1802,6 +1802,36 @@ void main() {
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
expect(right('title'), 708.0);
});
testWidgets('ListTile minTileHeight', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection, { double? minTileHeight, }) {
return MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: textDirection,
child: Material(
child: Container(
alignment: Alignment.topLeft,
child: ListTile(
minTileHeight: minTileHeight,
),
),
),
),
);
}
// Default list tile with height = 56.0
await tester.pumpWidget(buildFrame(TextDirection.ltr));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 56.0));
// Set list tile height = 30.0
await tester.pumpWidget(buildFrame(TextDirection.ltr, minTileHeight: 30));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 30.0));
// Set list tile height = 60.0
await tester.pumpWidget(buildFrame(TextDirection.ltr, minTileHeight: 60));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 60.0));
});
testWidgets('colors are applied to leading and trailing text widgets', (WidgetTester tester) async {
final Key leadingKey = UniqueKey();

View file

@ -72,6 +72,7 @@ void main() {
expect(themeData.horizontalTitleGap, null);
expect(themeData.minVerticalPadding, null);
expect(themeData.minLeadingWidth, null);
expect(themeData.minTileHeight, null);
expect(themeData.enableFeedback, null);
expect(themeData.mouseCursor, null);
expect(themeData.visualDensity, null);
@ -108,6 +109,7 @@ void main() {
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
minTileHeight: 30,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.comfortable,
@ -137,6 +139,7 @@ void main() {
'horizontalTitleGap: 200.0',
'minVerticalPadding: 300.0',
'minLeadingWidth: 400.0',
'minTileHeight: 30.0',
'enableFeedback: true',
'mouseCursor: WidgetStateMouseCursor(clickable)',
'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)',
@ -916,6 +919,7 @@ void main() {
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
minTileHeight: 30,
enableFeedback: true,
titleAlignment: ListTileTitleAlignment.bottom,
);
@ -936,6 +940,7 @@ void main() {
horizontalTitleGap: 600,
minVerticalPadding: 700,
minLeadingWidth: 800,
minTileHeight: 80,
enableFeedback: false,
titleAlignment: ListTileTitleAlignment.top,
);
@ -955,6 +960,7 @@ void main() {
expect(copy.horizontalTitleGap, 600);
expect(copy.minVerticalPadding, 700);
expect(copy.minLeadingWidth, 800);
expect(copy.minTileHeight, 80);
expect(copy.enableFeedback, false);
expect(copy.titleAlignment, ListTileTitleAlignment.top);
});
@ -1015,6 +1021,7 @@ void main() {
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
minTileHeight: 30,
enableFeedback: true,
titleAlignment: ListTileTitleAlignment.bottom,
mouseCursor: MaterialStateMouseCursor.textable,
@ -1041,6 +1048,7 @@ void main() {
horizontalTitleGap: 600,
minVerticalPadding: 700,
minLeadingWidth: 800,
minTileHeight: 80,
enableFeedback: false,
titleAlignment: ListTileTitleAlignment.top,
mouseCursor: MaterialStateMouseCursor.clickable,
@ -1071,6 +1079,7 @@ void main() {
expect(theme.horizontalTitleGap, 600);
expect(theme.minVerticalPadding, 700);
expect(theme.minLeadingWidth, 800);
expect(theme.minTileHeight, 80);
expect(theme.enableFeedback, false);
expect(theme.titleAlignment, ListTileTitleAlignment.top);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);