[reland] Add Material 3 support for ListTile
- Part 1 (#116963)
* [reland] Add Material 3 support for `ListTile` - Part 1 * Update doc
This commit is contained in:
parent
c63d797f94
commit
57fb36ee0a
@ -35,6 +35,7 @@ import 'package:gen_defaults/filter_chip_template.dart';
|
|||||||
import 'package:gen_defaults/icon_button_template.dart';
|
import 'package:gen_defaults/icon_button_template.dart';
|
||||||
import 'package:gen_defaults/input_chip_template.dart';
|
import 'package:gen_defaults/input_chip_template.dart';
|
||||||
import 'package:gen_defaults/input_decorator_template.dart';
|
import 'package:gen_defaults/input_decorator_template.dart';
|
||||||
|
import 'package:gen_defaults/list_tile_template.dart';
|
||||||
import 'package:gen_defaults/menu_template.dart';
|
import 'package:gen_defaults/menu_template.dart';
|
||||||
import 'package:gen_defaults/navigation_bar_template.dart';
|
import 'package:gen_defaults/navigation_bar_template.dart';
|
||||||
import 'package:gen_defaults/navigation_drawer_template.dart';
|
import 'package:gen_defaults/navigation_drawer_template.dart';
|
||||||
@ -155,6 +156,7 @@ Future<void> main(List<String> args) async {
|
|||||||
FilterChipTemplate('FilterChip', '$materialLib/filter_chip.dart', tokens).updateFile();
|
FilterChipTemplate('FilterChip', '$materialLib/filter_chip.dart', tokens).updateFile();
|
||||||
IconButtonTemplate('IconButton', '$materialLib/icon_button.dart', tokens).updateFile();
|
IconButtonTemplate('IconButton', '$materialLib/icon_button.dart', tokens).updateFile();
|
||||||
InputChipTemplate('InputChip', '$materialLib/input_chip.dart', tokens).updateFile();
|
InputChipTemplate('InputChip', '$materialLib/input_chip.dart', tokens).updateFile();
|
||||||
|
ListTileTemplate('LisTile', '$materialLib/list_tile.dart', tokens).updateFile();
|
||||||
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
|
InputDecoratorTemplate('InputDecorator', '$materialLib/input_decorator.dart', tokens).updateFile();
|
||||||
MenuTemplate('Menu', '$materialLib/menu_anchor.dart', tokens).updateFile();
|
MenuTemplate('Menu', '$materialLib/menu_anchor.dart', tokens).updateFile();
|
||||||
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
|
NavigationBarTemplate('NavigationBar', '$materialLib/navigation_bar.dart', tokens).updateFile();
|
||||||
|
37
dev/tools/gen_defaults/lib/list_tile_template.dart
Normal file
37
dev/tools/gen_defaults/lib/list_tile_template.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'template.dart';
|
||||||
|
|
||||||
|
class ListTileTemplate extends TokenTemplate {
|
||||||
|
const ListTileTemplate(super.blockName, super.fileName, super.tokens);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String generate() => '''
|
||||||
|
class _${blockName}DefaultsM3 extends ListTileThemeData {
|
||||||
|
const _${blockName}DefaultsM3(this.context)
|
||||||
|
: super(shape: ${shape("md.comp.list.list-item.container")});
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get tileColor => ${componentColor("md.comp.list.list-item.container")};
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get titleTextStyle => ${textStyle("md.comp.list.list-item.label-text")};
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get subtitleTextStyle => ${textStyle("md.comp.list.list-item.supporting-text")};
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get leadingAndTrailingTextStyle => ${textStyle("md.comp.list.list-item.trailing-supporting-text")};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get selectedColor => ${componentColor('md.comp.list.list-item.selected.trailing-icon')};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get iconColor => ${componentColor('md.comp.list.list-item.unselected.trailing-icon')};
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
}
|
@ -15,6 +15,7 @@ import 'ink_decoration.dart';
|
|||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
import 'list_tile_theme.dart';
|
import 'list_tile_theme.dart';
|
||||||
import 'material_state.dart';
|
import 'material_state.dart';
|
||||||
|
import 'text_theme.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import 'theme_data.dart';
|
import 'theme_data.dart';
|
||||||
|
|
||||||
@ -278,6 +279,9 @@ class ListTile extends StatelessWidget {
|
|||||||
this.selectedColor,
|
this.selectedColor,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
|
this.titleTextStyle,
|
||||||
|
this.subtitleTextStyle,
|
||||||
|
this.leadingAndTrailingTextStyle,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
@ -364,6 +368,8 @@ class ListTile extends StatelessWidget {
|
|||||||
/// If this property is null then its value is based on [ListTileTheme.dense].
|
/// If this property is null then its value is based on [ListTileTheme.dense].
|
||||||
///
|
///
|
||||||
/// Dense list tiles default to a smaller height.
|
/// Dense list tiles default to a smaller height.
|
||||||
|
///
|
||||||
|
/// It is not recommended to set [dense] to true when [ThemeData.useMaterial3] is true.
|
||||||
final bool? dense;
|
final bool? dense;
|
||||||
|
|
||||||
/// Defines how compact the list tile's layout will be.
|
/// Defines how compact the list tile's layout will be.
|
||||||
@ -421,6 +427,28 @@ class ListTile extends StatelessWidget {
|
|||||||
/// [ListTileThemeData].
|
/// [ListTileThemeData].
|
||||||
final Color? textColor;
|
final Color? textColor;
|
||||||
|
|
||||||
|
/// The text style for ListTile's [title].
|
||||||
|
///
|
||||||
|
/// If this property is null, then [ListTileThemeData.titleTextStyle] is used.
|
||||||
|
/// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.bodyLarge]
|
||||||
|
/// will be used. Otherwise, If ListTile style is [ListTileStyle.list],
|
||||||
|
/// [TextTheme.titleMedium] will be used and if ListTile style is [ListTileStyle.drawer],
|
||||||
|
/// [TextTheme.bodyLarge] will be used.
|
||||||
|
final TextStyle? titleTextStyle;
|
||||||
|
|
||||||
|
/// The text style for ListTile's [subtitle].
|
||||||
|
///
|
||||||
|
/// If this property is null, then [ListTileThemeData.subtitleTextStyle] is used.
|
||||||
|
/// If that is also null, [TextTheme.bodyMedium] will be used.
|
||||||
|
final TextStyle? subtitleTextStyle;
|
||||||
|
|
||||||
|
/// The text style for ListTile's [leading] and [trailing].
|
||||||
|
///
|
||||||
|
/// If this property is null, then [ListTileThemeData.leadingAndTrailingTextStyle] is used.
|
||||||
|
/// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.labelSmall]
|
||||||
|
/// will be used, otherwise [TextTheme.bodyMedium] will be used.
|
||||||
|
final TextStyle? leadingAndTrailingTextStyle;
|
||||||
|
|
||||||
/// Defines the font used for the [title].
|
/// Defines the font used for the [title].
|
||||||
///
|
///
|
||||||
/// If this property is null then [ListTileThemeData.style] is used. If that
|
/// If this property is null then [ListTileThemeData.style] is used. If that
|
||||||
@ -588,91 +616,16 @@ class ListTile extends StatelessWidget {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Color? _iconColor(ThemeData theme, ListTileThemeData tileTheme) {
|
|
||||||
if (!enabled) {
|
|
||||||
return theme.disabledColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
return selectedColor ?? tileTheme.selectedColor ?? theme.listTileTheme.selectedColor ?? theme.colorScheme.primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Color? color = iconColor
|
|
||||||
?? tileTheme.iconColor
|
|
||||||
?? theme.listTileTheme.iconColor
|
|
||||||
// If [ThemeData.useMaterial3] is set to true the disabled icon color
|
|
||||||
// will be set to Theme.colorScheme.onSurface(0.38), if false, defaults to null,
|
|
||||||
// as described in: https://m3.material.io/components/icon-buttons/specs.
|
|
||||||
?? (theme.useMaterial3 ? theme.colorScheme.onSurface.withOpacity(0.38) : null);
|
|
||||||
if (color != null) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (theme.brightness) {
|
|
||||||
case Brightness.light:
|
|
||||||
// For the sake of backwards compatibility, the default for unselected
|
|
||||||
// tiles is Colors.black45 rather than colorScheme.onSurface.withAlpha(0x73).
|
|
||||||
return Colors.black45;
|
|
||||||
case Brightness.dark:
|
|
||||||
return null; // null - use current icon theme color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Color? _textColor(ThemeData theme, ListTileThemeData tileTheme, Color? defaultColor) {
|
|
||||||
if (!enabled) {
|
|
||||||
return theme.disabledColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
return selectedColor ?? tileTheme.selectedColor ?? theme.listTileTheme.selectedColor ?? theme.colorScheme.primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
return textColor ?? tileTheme.textColor ?? theme.listTileTheme.textColor ?? defaultColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isDenseLayout(ThemeData theme, ListTileThemeData tileTheme) {
|
bool _isDenseLayout(ThemeData theme, ListTileThemeData tileTheme) {
|
||||||
return dense ?? tileTheme.dense ?? theme.listTileTheme.dense ?? false;
|
return dense ?? tileTheme.dense ?? theme.listTileTheme.dense ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _titleTextStyle(ThemeData theme, ListTileThemeData tileTheme) {
|
// TODO(TahaTesser): Refactor this to support list tile states.
|
||||||
final TextStyle textStyle;
|
Color _tileBackgroundColor(ThemeData theme, ListTileThemeData tileTheme, ListTileThemeData defaults) {
|
||||||
switch(style ?? tileTheme.style ?? theme.listTileTheme.style ?? ListTileStyle.list) {
|
|
||||||
case ListTileStyle.drawer:
|
|
||||||
textStyle = theme.useMaterial3 ? theme.textTheme.bodyMedium! : theme.textTheme.bodyLarge!;
|
|
||||||
break;
|
|
||||||
case ListTileStyle.list:
|
|
||||||
textStyle = theme.useMaterial3 ? theme.textTheme.titleMedium! : theme.textTheme.titleMedium!;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
final Color? color = _textColor(theme, tileTheme, textStyle.color);
|
|
||||||
return _isDenseLayout(theme, tileTheme)
|
|
||||||
? textStyle.copyWith(fontSize: 13.0, color: color)
|
|
||||||
: textStyle.copyWith(color: color);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle _subtitleTextStyle(ThemeData theme, ListTileThemeData tileTheme) {
|
|
||||||
final TextStyle textStyle = theme.useMaterial3 ? theme.textTheme.bodyMedium! : theme.textTheme.bodyMedium!;
|
|
||||||
final Color? color = _textColor(
|
|
||||||
theme,
|
|
||||||
tileTheme,
|
|
||||||
theme.useMaterial3 ? theme.textTheme.bodySmall!.color : theme.textTheme.bodySmall!.color,
|
|
||||||
);
|
|
||||||
return _isDenseLayout(theme, tileTheme)
|
|
||||||
? textStyle.copyWith(color: color, fontSize: 12.0)
|
|
||||||
: textStyle.copyWith(color: color);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle _trailingAndLeadingTextStyle(ThemeData theme, ListTileThemeData tileTheme) {
|
|
||||||
final TextStyle textStyle = theme.useMaterial3 ? theme.textTheme.bodyMedium! : theme.textTheme.bodyMedium!;
|
|
||||||
final Color? color = _textColor(theme, tileTheme, textStyle.color);
|
|
||||||
return textStyle.copyWith(color: color);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _tileBackgroundColor(ThemeData theme, ListTileThemeData tileTheme) {
|
|
||||||
final Color? color = selected
|
final Color? color = selected
|
||||||
? selectedTileColor ?? tileTheme.selectedTileColor ?? theme.listTileTheme.selectedTileColor
|
? selectedTileColor ?? tileTheme.selectedTileColor ?? theme.listTileTheme.selectedTileColor
|
||||||
: tileColor ?? tileTheme.tileColor ?? theme.listTileTheme.tileColor;
|
: tileColor ?? tileTheme.tileColor ?? theme.listTileTheme.tileColor;
|
||||||
return color ?? Colors.transparent;
|
return color ?? defaults.tileColor!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -680,23 +633,63 @@ class ListTile extends StatelessWidget {
|
|||||||
assert(debugCheckHasMaterial(context));
|
assert(debugCheckHasMaterial(context));
|
||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
final ListTileThemeData tileTheme = ListTileTheme.of(context);
|
final ListTileThemeData tileTheme = ListTileTheme.of(context);
|
||||||
final IconThemeData iconThemeData = IconThemeData(color: _iconColor(theme, tileTheme));
|
final ListTileStyle listTileStyle = style
|
||||||
|
?? tileTheme.style
|
||||||
|
?? theme.listTileTheme.style
|
||||||
|
?? ListTileStyle.list;
|
||||||
|
final ListTileThemeData defaults = theme.useMaterial3
|
||||||
|
? _LisTileDefaultsM3(context)
|
||||||
|
: _LisTileDefaultsM2(context, listTileStyle);
|
||||||
|
final Set<MaterialState> states = <MaterialState>{
|
||||||
|
if (!enabled) MaterialState.disabled,
|
||||||
|
if (selected) MaterialState.selected,
|
||||||
|
};
|
||||||
|
|
||||||
TextStyle? leadingAndTrailingTextStyle;
|
Color? resolveColor(Color? explicitColor, Color? selectedColor, Color? enabledColor, [Color? disabledColor]) {
|
||||||
|
return _IndividualOverrides(
|
||||||
|
explicitColor: explicitColor,
|
||||||
|
selectedColor: selectedColor,
|
||||||
|
enabledColor: enabledColor,
|
||||||
|
disabledColor: disabledColor,
|
||||||
|
).resolve(states);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Color? effectiveIconColor = resolveColor(iconColor, selectedColor, iconColor)
|
||||||
|
?? resolveColor(tileTheme.iconColor, tileTheme.selectedColor, tileTheme.iconColor)
|
||||||
|
?? resolveColor(theme.listTileTheme.iconColor, theme.listTileTheme.selectedColor, theme.listTileTheme.iconColor)
|
||||||
|
?? resolveColor(defaults.iconColor, defaults.selectedColor, defaults.iconColor, theme.disabledColor);
|
||||||
|
final Color? effectiveColor = resolveColor(textColor, selectedColor, textColor)
|
||||||
|
?? resolveColor(tileTheme.textColor, tileTheme.selectedColor, tileTheme.textColor)
|
||||||
|
?? resolveColor(theme.listTileTheme.textColor, theme.listTileTheme.selectedColor, theme.listTileTheme.textColor)
|
||||||
|
?? resolveColor(defaults.textColor, defaults.selectedColor, defaults.textColor, theme.disabledColor);
|
||||||
|
final IconThemeData iconThemeData = IconThemeData(color: effectiveIconColor);
|
||||||
|
|
||||||
|
TextStyle? leadingAndTrailingStyle;
|
||||||
if (leading != null || trailing != null) {
|
if (leading != null || trailing != null) {
|
||||||
leadingAndTrailingTextStyle = _trailingAndLeadingTextStyle(theme, tileTheme);
|
leadingAndTrailingStyle = leadingAndTrailingTextStyle
|
||||||
|
?? tileTheme.leadingAndTrailingTextStyle
|
||||||
|
?? defaults.leadingAndTrailingTextStyle!;
|
||||||
|
final Color? leadingAndTrailingTextColor = effectiveColor;
|
||||||
|
leadingAndTrailingStyle = leadingAndTrailingStyle.copyWith(color: leadingAndTrailingTextColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? leadingIcon;
|
Widget? leadingIcon;
|
||||||
if (leading != null) {
|
if (leading != null) {
|
||||||
leadingIcon = AnimatedDefaultTextStyle(
|
leadingIcon = AnimatedDefaultTextStyle(
|
||||||
style: leadingAndTrailingTextStyle!,
|
style: leadingAndTrailingStyle!,
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: leading!,
|
child: leading!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextStyle titleStyle = _titleTextStyle(theme, tileTheme);
|
TextStyle titleStyle = titleTextStyle
|
||||||
|
?? tileTheme.titleTextStyle
|
||||||
|
?? defaults.titleTextStyle!;
|
||||||
|
final Color? titleColor = effectiveColor;
|
||||||
|
titleStyle = titleStyle.copyWith(
|
||||||
|
color: titleColor,
|
||||||
|
fontSize: _isDenseLayout(theme, tileTheme) ? 13.0 : null,
|
||||||
|
);
|
||||||
final Widget titleText = AnimatedDefaultTextStyle(
|
final Widget titleText = AnimatedDefaultTextStyle(
|
||||||
style: titleStyle,
|
style: titleStyle,
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
@ -706,7 +699,14 @@ class ListTile extends StatelessWidget {
|
|||||||
Widget? subtitleText;
|
Widget? subtitleText;
|
||||||
TextStyle? subtitleStyle;
|
TextStyle? subtitleStyle;
|
||||||
if (subtitle != null) {
|
if (subtitle != null) {
|
||||||
subtitleStyle = _subtitleTextStyle(theme, tileTheme);
|
subtitleStyle = subtitleTextStyle
|
||||||
|
?? tileTheme.subtitleTextStyle
|
||||||
|
?? defaults.subtitleTextStyle!;
|
||||||
|
final Color? subtitleColor = effectiveColor ?? theme.textTheme.bodySmall!.color;
|
||||||
|
subtitleStyle = subtitleStyle.copyWith(
|
||||||
|
color: subtitleColor,
|
||||||
|
fontSize: _isDenseLayout(theme, tileTheme) ? 12.0 : null,
|
||||||
|
);
|
||||||
subtitleText = AnimatedDefaultTextStyle(
|
subtitleText = AnimatedDefaultTextStyle(
|
||||||
style: subtitleStyle,
|
style: subtitleStyle,
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
@ -717,7 +717,7 @@ class ListTile extends StatelessWidget {
|
|||||||
Widget? trailingIcon;
|
Widget? trailingIcon;
|
||||||
if (trailing != null) {
|
if (trailing != null) {
|
||||||
trailingIcon = AnimatedDefaultTextStyle(
|
trailingIcon = AnimatedDefaultTextStyle(
|
||||||
style: leadingAndTrailingTextStyle!,
|
style: leadingAndTrailingStyle!,
|
||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: trailing!,
|
child: trailing!,
|
||||||
);
|
);
|
||||||
@ -728,15 +728,13 @@ class ListTile extends StatelessWidget {
|
|||||||
final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
|
final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
|
||||||
?? tileTheme.contentPadding?.resolve(textDirection)
|
?? tileTheme.contentPadding?.resolve(textDirection)
|
||||||
?? defaultContentPadding;
|
?? defaultContentPadding;
|
||||||
|
// Show basic cursor when ListTile isn't enabled or gesture callbacks are null.
|
||||||
final Set<MaterialState> states = <MaterialState>{
|
final Set<MaterialState> mouseStates = <MaterialState>{
|
||||||
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
|
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
|
||||||
if (selected) MaterialState.selected,
|
|
||||||
};
|
};
|
||||||
|
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(mouseCursor, mouseStates)
|
||||||
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(mouseCursor, states)
|
?? tileTheme.mouseCursor?.resolve(mouseStates)
|
||||||
?? tileTheme.mouseCursor?.resolve(states)
|
?? MaterialStateMouseCursor.clickable.resolve(mouseStates);
|
||||||
?? MaterialStateMouseCursor.clickable.resolve(states);
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
customBorder: shape ?? tileTheme.shape,
|
customBorder: shape ?? tileTheme.shape,
|
||||||
@ -757,7 +755,7 @@ class ListTile extends StatelessWidget {
|
|||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
shape: shape ?? tileTheme.shape ?? const Border(),
|
shape: shape ?? tileTheme.shape ?? const Border(),
|
||||||
color: _tileBackgroundColor(theme, tileTheme),
|
color: _tileBackgroundColor(theme, tileTheme, defaults),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
@ -774,8 +772,8 @@ class ListTile extends StatelessWidget {
|
|||||||
visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
|
visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
|
||||||
isThreeLine: isThreeLine,
|
isThreeLine: isThreeLine,
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
titleBaselineType: titleStyle.textBaseline!,
|
titleBaselineType: titleStyle.textBaseline ?? defaults.titleTextStyle!.textBaseline!,
|
||||||
subtitleBaselineType: subtitleStyle?.textBaseline,
|
subtitleBaselineType: subtitleStyle?.textBaseline ?? defaults.subtitleTextStyle!.textBaseline!,
|
||||||
horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
|
horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
|
||||||
minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? 4,
|
minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? 4,
|
||||||
minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? 40,
|
minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? 40,
|
||||||
@ -821,6 +819,36 @@ class ListTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _IndividualOverrides extends MaterialStateProperty<Color?> {
|
||||||
|
_IndividualOverrides({
|
||||||
|
this.explicitColor,
|
||||||
|
this.enabledColor,
|
||||||
|
this.selectedColor,
|
||||||
|
this.disabledColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Color? explicitColor;
|
||||||
|
final Color? enabledColor;
|
||||||
|
final Color? selectedColor;
|
||||||
|
final Color? disabledColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? resolve(Set<MaterialState> states) {
|
||||||
|
if (explicitColor is MaterialStateColor) {
|
||||||
|
return MaterialStateProperty.resolveAs<Color?>(explicitColor, states);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return disabledColor;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabledColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Identifies the children of a _ListTileElement.
|
// Identifies the children of a _ListTileElement.
|
||||||
enum _ListTileSlot {
|
enum _ListTileSlot {
|
||||||
leading,
|
leading,
|
||||||
@ -1343,3 +1371,87 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LisTileDefaultsM2 extends ListTileThemeData {
|
||||||
|
_LisTileDefaultsM2(this.context, ListTileStyle style)
|
||||||
|
: _themeData = Theme.of(context),
|
||||||
|
_textTheme = Theme.of(context).textTheme,
|
||||||
|
super(
|
||||||
|
shape: const Border(),
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
final ThemeData _themeData;
|
||||||
|
final TextTheme _textTheme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get tileColor => Colors.transparent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get titleTextStyle {
|
||||||
|
switch (style!) {
|
||||||
|
case ListTileStyle.drawer:
|
||||||
|
return _textTheme.bodyLarge;
|
||||||
|
case ListTileStyle.list:
|
||||||
|
return _textTheme.titleMedium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get subtitleTextStyle => _textTheme.bodyMedium;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get leadingAndTrailingTextStyle => _textTheme.bodyMedium;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get selectedColor => _themeData.colorScheme.primary;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get iconColor {
|
||||||
|
switch (_themeData.brightness) {
|
||||||
|
case Brightness.light:
|
||||||
|
// For the sake of backwards compatibility, the default for unselected
|
||||||
|
// tiles is Colors.black45 rather than colorScheme.onSurface.withAlpha(0x73).
|
||||||
|
return Colors.black45;
|
||||||
|
case Brightness.dark:
|
||||||
|
return null; // null, Use current icon theme color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BEGIN GENERATED TOKEN PROPERTIES - LisTile
|
||||||
|
|
||||||
|
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||||||
|
// "END GENERATED" comments are generated from data in the Material
|
||||||
|
// Design token database by the script:
|
||||||
|
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||||
|
|
||||||
|
// Token database version: v0_143
|
||||||
|
|
||||||
|
class _LisTileDefaultsM3 extends ListTileThemeData {
|
||||||
|
const _LisTileDefaultsM3(this.context)
|
||||||
|
: super(shape: const RoundedRectangleBorder());
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get tileColor => Theme.of(context).colorScheme.surface;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get titleTextStyle => Theme.of(context).textTheme.bodyLarge;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get subtitleTextStyle => Theme.of(context).textTheme.bodyMedium;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle? get leadingAndTrailingTextStyle => Theme.of(context).textTheme.labelSmall;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get selectedColor => Theme.of(context).colorScheme.primary;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get iconColor => Theme.of(context).colorScheme.onSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
// END GENERATED TOKEN PROPERTIES - LisTile
|
||||||
|
@ -51,6 +51,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
this.selectedColor,
|
this.selectedColor,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
|
this.titleTextStyle,
|
||||||
|
this.subtitleTextStyle,
|
||||||
|
this.leadingAndTrailingTextStyle,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.tileColor,
|
this.tileColor,
|
||||||
this.selectedTileColor,
|
this.selectedTileColor,
|
||||||
@ -80,6 +83,15 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
/// Overrides the default value of [ListTile.textColor].
|
/// Overrides the default value of [ListTile.textColor].
|
||||||
final Color? textColor;
|
final Color? textColor;
|
||||||
|
|
||||||
|
/// Overrides the default value of [ListTile.titleTextStyle].
|
||||||
|
final TextStyle? titleTextStyle;
|
||||||
|
|
||||||
|
/// Overrides the default value of [ListTile.subtitleTextStyle].
|
||||||
|
final TextStyle? subtitleTextStyle;
|
||||||
|
|
||||||
|
/// Overrides the default value of [ListTile.leadingAndTrailingTextStyle].
|
||||||
|
final TextStyle? leadingAndTrailingTextStyle;
|
||||||
|
|
||||||
/// Overrides the default value of [ListTile.contentPadding].
|
/// Overrides the default value of [ListTile.contentPadding].
|
||||||
final EdgeInsetsGeometry? contentPadding;
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
|
|
||||||
@ -116,6 +128,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
Color? selectedColor,
|
Color? selectedColor,
|
||||||
Color? iconColor,
|
Color? iconColor,
|
||||||
Color? textColor,
|
Color? textColor,
|
||||||
|
TextStyle? titleTextStyle,
|
||||||
|
TextStyle? subtitleTextStyle,
|
||||||
|
TextStyle? leadingAndTrailingTextStyle,
|
||||||
EdgeInsetsGeometry? contentPadding,
|
EdgeInsetsGeometry? contentPadding,
|
||||||
Color? tileColor,
|
Color? tileColor,
|
||||||
Color? selectedTileColor,
|
Color? selectedTileColor,
|
||||||
@ -134,6 +149,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
selectedColor: selectedColor ?? this.selectedColor,
|
selectedColor: selectedColor ?? this.selectedColor,
|
||||||
iconColor: iconColor ?? this.iconColor,
|
iconColor: iconColor ?? this.iconColor,
|
||||||
textColor: textColor ?? this.textColor,
|
textColor: textColor ?? this.textColor,
|
||||||
|
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
|
||||||
|
subtitleTextStyle: titleTextStyle ?? this.subtitleTextStyle,
|
||||||
|
leadingAndTrailingTextStyle: titleTextStyle ?? this.leadingAndTrailingTextStyle,
|
||||||
contentPadding: contentPadding ?? this.contentPadding,
|
contentPadding: contentPadding ?? this.contentPadding,
|
||||||
tileColor: tileColor ?? this.tileColor,
|
tileColor: tileColor ?? this.tileColor,
|
||||||
selectedTileColor: selectedTileColor ?? this.selectedTileColor,
|
selectedTileColor: selectedTileColor ?? this.selectedTileColor,
|
||||||
@ -159,6 +177,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
|
selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
|
||||||
iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
|
iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
|
||||||
textColor: Color.lerp(a?.textColor, b?.textColor, t),
|
textColor: Color.lerp(a?.textColor, b?.textColor, t),
|
||||||
|
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
|
||||||
|
subtitleTextStyle: TextStyle.lerp(a?.subtitleTextStyle, b?.subtitleTextStyle, t),
|
||||||
|
leadingAndTrailingTextStyle: TextStyle.lerp(a?.leadingAndTrailingTextStyle, b?.leadingAndTrailingTextStyle, t),
|
||||||
contentPadding: EdgeInsetsGeometry.lerp(a?.contentPadding, b?.contentPadding, t),
|
contentPadding: EdgeInsetsGeometry.lerp(a?.contentPadding, b?.contentPadding, t),
|
||||||
tileColor: Color.lerp(a?.tileColor, b?.tileColor, t),
|
tileColor: Color.lerp(a?.tileColor, b?.tileColor, t),
|
||||||
selectedTileColor: Color.lerp(a?.selectedTileColor, b?.selectedTileColor, t),
|
selectedTileColor: Color.lerp(a?.selectedTileColor, b?.selectedTileColor, t),
|
||||||
@ -179,6 +200,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
selectedColor,
|
selectedColor,
|
||||||
iconColor,
|
iconColor,
|
||||||
textColor,
|
textColor,
|
||||||
|
titleTextStyle,
|
||||||
|
subtitleTextStyle,
|
||||||
|
leadingAndTrailingTextStyle,
|
||||||
contentPadding,
|
contentPadding,
|
||||||
tileColor,
|
tileColor,
|
||||||
selectedTileColor,
|
selectedTileColor,
|
||||||
@ -204,6 +228,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
&& other.style == style
|
&& other.style == style
|
||||||
&& other.selectedColor == selectedColor
|
&& other.selectedColor == selectedColor
|
||||||
&& other.iconColor == iconColor
|
&& other.iconColor == iconColor
|
||||||
|
&& other.titleTextStyle == titleTextStyle
|
||||||
|
&& other.subtitleTextStyle == subtitleTextStyle
|
||||||
|
&& other.leadingAndTrailingTextStyle == leadingAndTrailingTextStyle
|
||||||
&& other.textColor == textColor
|
&& other.textColor == textColor
|
||||||
&& other.contentPadding == contentPadding
|
&& other.contentPadding == contentPadding
|
||||||
&& other.tileColor == tileColor
|
&& other.tileColor == tileColor
|
||||||
@ -225,6 +252,9 @@ class ListTileThemeData with Diagnosticable {
|
|||||||
properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
|
properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
|
||||||
properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
|
properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
|
||||||
properties.add(ColorProperty('textColor', textColor, defaultValue: null));
|
properties.add(ColorProperty('textColor', textColor, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<TextStyle>('subtitleTextStyle', subtitleTextStyle, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<TextStyle>('leadingAndTrailingTextStyle', leadingAndTrailingTextStyle, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: null));
|
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: null));
|
||||||
properties.add(ColorProperty('tileColor', tileColor, defaultValue: null));
|
properties.add(ColorProperty('tileColor', tileColor, defaultValue: null));
|
||||||
properties.add(ColorProperty('selectedTileColor', selectedTileColor, defaultValue: null));
|
properties.add(ColorProperty('selectedTileColor', selectedTileColor, defaultValue: null));
|
||||||
|
@ -1578,10 +1578,11 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('ListTile default tile color', (WidgetTester tester) async {
|
testWidgets('ListTile default tile color', (WidgetTester tester) async {
|
||||||
bool isSelected = false;
|
bool isSelected = false;
|
||||||
const Color defaultColor = Colors.transparent;
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: StatefulBuilder(
|
child: StatefulBuilder(
|
||||||
@ -1600,13 +1601,13 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(find.byType(Material), paints..rect(color: defaultColor));
|
expect(find.byType(Material), paints..rect(color: theme.colorScheme.surface));
|
||||||
|
|
||||||
// Tap on tile to change isSelected.
|
// Tap on tile to change isSelected.
|
||||||
await tester.tap(find.byType(ListTile));
|
await tester.tap(find.byType(ListTile));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(Material), paints..rect(color: defaultColor));
|
expect(find.byType(Material), paints..rect(color: theme.colorScheme.surface));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('ListTile layout at zero size', (WidgetTester tester) async {
|
testWidgets('ListTile layout at zero size', (WidgetTester tester) async {
|
||||||
@ -2064,18 +2065,15 @@ void main() {
|
|||||||
expect(textColor(trailingKey), theme.disabledColor);
|
expect(textColor(trailingKey), theme.disabledColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('selected, enabled ListTile default icon color, light and dark themes', (WidgetTester tester) async {
|
testWidgets('selected, enabled ListTile default icon color', (WidgetTester tester) async {
|
||||||
const ColorScheme lightColorScheme = ColorScheme.light();
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
const ColorScheme darkColorScheme = ColorScheme.dark();
|
final ColorScheme colorScheme = theme.colorScheme;
|
||||||
final Key leadingKey = UniqueKey();
|
final Key leadingKey = UniqueKey();
|
||||||
final Key titleKey = UniqueKey();
|
final Key titleKey = UniqueKey();
|
||||||
final Key subtitleKey = UniqueKey();
|
final Key subtitleKey = UniqueKey();
|
||||||
final Key trailingKey = UniqueKey();
|
final Key trailingKey = UniqueKey();
|
||||||
|
|
||||||
Widget buildFrame({ required Brightness brightness, required bool selected }) {
|
Widget buildFrame({required bool selected }) {
|
||||||
final ThemeData theme = brightness == Brightness.light
|
|
||||||
? ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true)
|
|
||||||
: ThemeData.from(colorScheme: const ColorScheme.dark(), useMaterial3: true);
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
home: Material(
|
home: Material(
|
||||||
@ -2094,56 +2092,32 @@ void main() {
|
|||||||
|
|
||||||
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
|
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
|
||||||
|
|
||||||
await tester.pumpWidget(buildFrame(brightness: Brightness.light, selected: true));
|
await tester.pumpWidget(buildFrame(selected: true));
|
||||||
expect(iconColor(leadingKey), lightColorScheme.primary);
|
expect(iconColor(leadingKey), colorScheme.primary);
|
||||||
expect(iconColor(titleKey), lightColorScheme.primary);
|
expect(iconColor(titleKey), colorScheme.primary);
|
||||||
expect(iconColor(subtitleKey), lightColorScheme.primary);
|
expect(iconColor(subtitleKey), colorScheme.primary);
|
||||||
expect(iconColor(trailingKey), lightColorScheme.primary);
|
expect(iconColor(trailingKey), colorScheme.primary);
|
||||||
|
|
||||||
await tester.pumpWidget(buildFrame(brightness: Brightness.light, selected: false));
|
await tester.pumpWidget(buildFrame(selected: false));
|
||||||
expect(iconColor(leadingKey), lightColorScheme.onSurface.withOpacity(0.38));
|
expect(iconColor(leadingKey), colorScheme.onSurface);
|
||||||
expect(iconColor(titleKey), lightColorScheme.onSurface.withOpacity(0.38));
|
expect(iconColor(titleKey), colorScheme.onSurface);
|
||||||
expect(iconColor(subtitleKey), lightColorScheme.onSurface.withOpacity(0.38));
|
expect(iconColor(subtitleKey), colorScheme.onSurface);
|
||||||
expect(iconColor(trailingKey), lightColorScheme.onSurface.withOpacity(0.38));
|
expect(iconColor(trailingKey), colorScheme.onSurface);
|
||||||
|
|
||||||
await tester.pumpWidget(buildFrame(brightness: Brightness.dark, selected: true));
|
|
||||||
await tester.pumpAndSettle(); // Animated theme change
|
|
||||||
expect(iconColor(leadingKey), darkColorScheme.primary);
|
|
||||||
expect(iconColor(titleKey), darkColorScheme.primary);
|
|
||||||
expect(iconColor(subtitleKey), darkColorScheme.primary);
|
|
||||||
expect(iconColor(trailingKey), darkColorScheme.primary);
|
|
||||||
|
|
||||||
// For this configuration, ListTile defers to the default IconTheme.
|
|
||||||
// The default dark theme's IconTheme has color:white
|
|
||||||
await tester.pumpWidget(buildFrame(brightness: Brightness.dark, selected: false));
|
|
||||||
expect(iconColor(leadingKey), darkColorScheme.onSurface.withOpacity(0.38));
|
|
||||||
expect(iconColor(titleKey), darkColorScheme.onSurface.withOpacity(0.38));
|
|
||||||
expect(iconColor(subtitleKey), darkColorScheme.onSurface.withOpacity(0.38));
|
|
||||||
expect(iconColor(trailingKey), darkColorScheme.onSurface.withOpacity(0.38));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('ListTile font size', (WidgetTester tester) async {
|
testWidgets('ListTile font size', (WidgetTester tester) async {
|
||||||
Widget buildFrame({
|
Widget buildFrame() {
|
||||||
bool dense = false,
|
|
||||||
bool enabled = true,
|
|
||||||
bool selected = false,
|
|
||||||
ListTileStyle? style,
|
|
||||||
}) {
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeData(useMaterial3: true),
|
theme: ThemeData(useMaterial3: true),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return ListTile(
|
return const ListTile(
|
||||||
dense: dense,
|
leading: TestText('leading'),
|
||||||
enabled: enabled,
|
title: TestText('title'),
|
||||||
selected: selected,
|
subtitle: TestText('subtitle') ,
|
||||||
style: style,
|
trailing: TestText('trailing'),
|
||||||
leading: const TestText('leading'),
|
|
||||||
title: const TestText('title'),
|
|
||||||
subtitle: const TestText('subtitle') ,
|
|
||||||
trailing: const TestText('trailing'),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -2152,76 +2126,31 @@ void main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTile - ListTileStyle.list (default).
|
// ListTile default text sizes.
|
||||||
await tester.pumpWidget(buildFrame());
|
await tester.pumpWidget(buildFrame());
|
||||||
RenderParagraph leading = _getTextRenderObject(tester, 'leading');
|
final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
|
||||||
expect(leading.text.style!.fontSize, 14.0);
|
expect(leading.text.style!.fontSize, 11.0);
|
||||||
RenderParagraph title = _getTextRenderObject(tester, 'title');
|
final RenderParagraph title = _getTextRenderObject(tester, 'title');
|
||||||
expect(title.text.style!.fontSize, 16.0);
|
expect(title.text.style!.fontSize, 16.0);
|
||||||
RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
|
final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
|
||||||
expect(subtitle.text.style!.fontSize, 14.0);
|
expect(subtitle.text.style!.fontSize, 14.0);
|
||||||
RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
|
final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
|
||||||
expect(trailing.text.style!.fontSize, 14.0);
|
expect(trailing.text.style!.fontSize, 11.0);
|
||||||
|
|
||||||
// ListTile - Densed - ListTileStyle.list (default).
|
|
||||||
await tester.pumpWidget(buildFrame(dense: true));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
leading = _getTextRenderObject(tester, 'leading');
|
|
||||||
expect(leading.text.style!.fontSize, 14.0);
|
|
||||||
title = _getTextRenderObject(tester, 'title');
|
|
||||||
expect(title.text.style!.fontSize, 13.0);
|
|
||||||
subtitle = _getTextRenderObject(tester, 'subtitle');
|
|
||||||
expect(subtitle.text.style!.fontSize, 12.0);
|
|
||||||
trailing = _getTextRenderObject(tester, 'trailing');
|
|
||||||
expect(trailing.text.style!.fontSize, 14.0);
|
|
||||||
|
|
||||||
// ListTile - ListTileStyle.drawer.
|
|
||||||
await tester.pumpWidget(buildFrame(style: ListTileStyle.drawer));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
leading = _getTextRenderObject(tester, 'leading');
|
|
||||||
expect(leading.text.style!.fontSize, 14.0);
|
|
||||||
title = _getTextRenderObject(tester, 'title');
|
|
||||||
expect(title.text.style!.fontSize, 14.0);
|
|
||||||
subtitle = _getTextRenderObject(tester, 'subtitle');
|
|
||||||
expect(subtitle.text.style!.fontSize, 14.0);
|
|
||||||
trailing = _getTextRenderObject(tester, 'trailing');
|
|
||||||
expect(trailing.text.style!.fontSize, 14.0);
|
|
||||||
|
|
||||||
// ListTile - Densed - ListTileStyle.drawer.
|
|
||||||
await tester.pumpWidget(buildFrame(dense: true, style: ListTileStyle.drawer));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
leading = _getTextRenderObject(tester, 'leading');
|
|
||||||
expect(leading.text.style!.fontSize, 14.0);
|
|
||||||
title = _getTextRenderObject(tester, 'title');
|
|
||||||
expect(title.text.style!.fontSize, 13.0);
|
|
||||||
subtitle = _getTextRenderObject(tester, 'subtitle');
|
|
||||||
expect(subtitle.text.style!.fontSize, 12.0);
|
|
||||||
trailing = _getTextRenderObject(tester, 'trailing');
|
|
||||||
expect(trailing.text.style!.fontSize, 14.0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('ListTile text color', (WidgetTester tester) async {
|
testWidgets('ListTile text color', (WidgetTester tester) async {
|
||||||
Widget buildFrame({
|
Widget buildFrame() {
|
||||||
bool dense = false,
|
|
||||||
bool enabled = true,
|
|
||||||
bool selected = false,
|
|
||||||
ListTileStyle? style,
|
|
||||||
}) {
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeData(useMaterial3: true),
|
theme: ThemeData(useMaterial3: true),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return ListTile(
|
return const ListTile(
|
||||||
dense: dense,
|
leading: TestText('leading'),
|
||||||
enabled: enabled,
|
title: TestText('title'),
|
||||||
selected: selected,
|
subtitle: TestText('subtitle') ,
|
||||||
style: style,
|
trailing: TestText('trailing'),
|
||||||
leading: const TestText('leading'),
|
|
||||||
title: const TestText('title'),
|
|
||||||
subtitle: const TestText('subtitle') ,
|
|
||||||
trailing: const TestText('trailing'),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -2232,28 +2161,16 @@ void main() {
|
|||||||
|
|
||||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
|
||||||
// ListTile - ListTileStyle.list (default).
|
// ListTile default text colors.
|
||||||
await tester.pumpWidget(buildFrame());
|
await tester.pumpWidget(buildFrame());
|
||||||
RenderParagraph leading = _getTextRenderObject(tester, 'leading');
|
final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
|
||||||
expect(leading.text.style!.color, theme.textTheme.bodyMedium!.color);
|
expect(leading.text.style!.color, theme.textTheme.labelSmall!.color);
|
||||||
RenderParagraph title = _getTextRenderObject(tester, 'title');
|
final RenderParagraph title = _getTextRenderObject(tester, 'title');
|
||||||
expect(title.text.style!.color, theme.textTheme.titleMedium!.color);
|
|
||||||
RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
|
|
||||||
expect(subtitle.text.style!.color, theme.textTheme.bodySmall!.color);
|
|
||||||
RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
|
|
||||||
expect(trailing.text.style!.color, theme.textTheme.bodyMedium!.color);
|
|
||||||
|
|
||||||
// ListTile - ListTileStyle.drawer.
|
|
||||||
await tester.pumpWidget(buildFrame(style: ListTileStyle.drawer));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
leading = _getTextRenderObject(tester, 'leading');
|
|
||||||
expect(leading.text.style!.color, theme.textTheme.bodyMedium!.color);
|
|
||||||
title = _getTextRenderObject(tester, 'title');
|
|
||||||
expect(title.text.style!.color, theme.textTheme.bodyLarge!.color);
|
expect(title.text.style!.color, theme.textTheme.bodyLarge!.color);
|
||||||
subtitle = _getTextRenderObject(tester, 'subtitle');
|
final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
|
||||||
expect(subtitle.text.style!.color, theme.textTheme.bodySmall!.color);
|
expect(subtitle.text.style!.color, theme.textTheme.bodyMedium!.color);
|
||||||
trailing = _getTextRenderObject(tester, 'trailing');
|
final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
|
||||||
expect(trailing.text.style!.color, theme.textTheme.bodyMedium!.color);
|
expect(trailing.text.style!.color, theme.textTheme.labelSmall!.color);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Default ListTile debugFillProperties', (WidgetTester tester) async {
|
testWidgets('Default ListTile debugFillProperties', (WidgetTester tester) async {
|
||||||
@ -2333,6 +2250,149 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTile.textColor respects MaterialStateColor', (WidgetTester tester) async {
|
||||||
|
bool enabled = false;
|
||||||
|
bool selected = false;
|
||||||
|
const Color defaultColor = Colors.blue;
|
||||||
|
const Color selectedColor = Colors.green;
|
||||||
|
const Color disabledColor = Colors.red;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true),
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
enabled: enabled,
|
||||||
|
selected: selected,
|
||||||
|
textColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return disabledColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultColor;
|
||||||
|
}),
|
||||||
|
title: const TestText('title'),
|
||||||
|
subtitle: const TestText('subtitle') ,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test disabled state.
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
RenderParagraph title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.color, disabledColor);
|
||||||
|
|
||||||
|
// Test enabled state.
|
||||||
|
enabled = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.color, defaultColor);
|
||||||
|
|
||||||
|
// Test selected state.
|
||||||
|
selected = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.color, selectedColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTile.iconColor respects MaterialStateColor', (WidgetTester tester) async {
|
||||||
|
bool enabled = false;
|
||||||
|
bool selected = false;
|
||||||
|
const Color defaultColor = Colors.blue;
|
||||||
|
const Color selectedColor = Colors.green;
|
||||||
|
const Color disabledColor = Colors.red;
|
||||||
|
final Key leadingKey = UniqueKey();
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true),
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
enabled: enabled,
|
||||||
|
selected: selected,
|
||||||
|
iconColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return disabledColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultColor;
|
||||||
|
}),
|
||||||
|
leading: TestIcon(key: leadingKey),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
|
||||||
|
|
||||||
|
// Test disabled state.
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
expect(iconColor(leadingKey), disabledColor);
|
||||||
|
|
||||||
|
// Test enabled state.
|
||||||
|
enabled = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(iconColor(leadingKey), defaultColor);
|
||||||
|
|
||||||
|
// Test selected state.
|
||||||
|
selected = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(iconColor(leadingKey), selectedColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTile.dense does not throw assertion', (WidgetTester tester) async {
|
||||||
|
// This is a regression test for https://github.com/flutter/flutter/pull/116908
|
||||||
|
|
||||||
|
Widget buildFrame({required bool useMaterial3}) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: useMaterial3),
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return const ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text('Title'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame(useMaterial3: false));
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame(useMaterial3: true));
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
group('Material 2', () {
|
group('Material 2', () {
|
||||||
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
||||||
// is turned on by default, these tests can be removed.
|
// is turned on by default, these tests can be removed.
|
||||||
@ -2345,6 +2405,7 @@ void main() {
|
|||||||
ListTileStyle? style,
|
ListTileStyle? style,
|
||||||
}) {
|
}) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: false),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
@ -2422,6 +2483,7 @@ void main() {
|
|||||||
ListTileStyle? style,
|
ListTileStyle? style,
|
||||||
}) {
|
}) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: false),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
@ -2481,8 +2543,8 @@ void main() {
|
|||||||
|
|
||||||
Widget buildFrame({ required Brightness brightness, required bool selected }) {
|
Widget buildFrame({ required Brightness brightness, required bool selected }) {
|
||||||
final ThemeData theme = brightness == Brightness.light
|
final ThemeData theme = brightness == Brightness.light
|
||||||
? ThemeData.from(colorScheme: const ColorScheme.light())
|
? ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: false)
|
||||||
: ThemeData.from(colorScheme: const ColorScheme.dark());
|
: ThemeData.from(colorScheme: const ColorScheme.dark(), useMaterial3: false);
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
home: Material(
|
home: Material(
|
||||||
@ -2528,6 +2590,40 @@ void main() {
|
|||||||
expect(iconColor(subtitleKey), Colors.white);
|
expect(iconColor(subtitleKey), Colors.white);
|
||||||
expect(iconColor(trailingKey), Colors.white);
|
expect(iconColor(trailingKey), Colors.white);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTile default tile color', (WidgetTester tester) async {
|
||||||
|
bool isSelected = false;
|
||||||
|
const Color defaultColor = Colors.transparent;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: false),
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return ListTile(
|
||||||
|
selected: isSelected,
|
||||||
|
onTap: () {
|
||||||
|
setState(()=> isSelected = !isSelected);
|
||||||
|
},
|
||||||
|
title: const Text('Title'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byType(Material), paints..rect(color: defaultColor));
|
||||||
|
|
||||||
|
// Tap on tile to change isSelected.
|
||||||
|
await tester.tap(find.byType(ListTile));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(Material), paints..rect(color: defaultColor));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,9 @@ void main() {
|
|||||||
expect(themeData.selectedColor, null);
|
expect(themeData.selectedColor, null);
|
||||||
expect(themeData.iconColor, null);
|
expect(themeData.iconColor, null);
|
||||||
expect(themeData.textColor, null);
|
expect(themeData.textColor, null);
|
||||||
|
expect(themeData.titleTextStyle, null);
|
||||||
|
expect(themeData.subtitleTextStyle, null);
|
||||||
|
expect(themeData.leadingAndTrailingTextStyle, null);
|
||||||
expect(themeData.contentPadding, null);
|
expect(themeData.contentPadding, null);
|
||||||
expect(themeData.tileColor, null);
|
expect(themeData.tileColor, null);
|
||||||
expect(themeData.selectedTileColor, null);
|
expect(themeData.selectedTileColor, null);
|
||||||
@ -91,9 +94,12 @@ void main() {
|
|||||||
selectedColor: Color(0x00000001),
|
selectedColor: Color(0x00000001),
|
||||||
iconColor: Color(0x00000002),
|
iconColor: Color(0x00000002),
|
||||||
textColor: Color(0x00000003),
|
textColor: Color(0x00000003),
|
||||||
|
titleTextStyle: TextStyle(color: Color(0x00000004)),
|
||||||
|
subtitleTextStyle: TextStyle(color: Color(0x00000005)),
|
||||||
|
leadingAndTrailingTextStyle: TextStyle(color: Color(0x00000006)),
|
||||||
contentPadding: EdgeInsets.all(100),
|
contentPadding: EdgeInsets.all(100),
|
||||||
tileColor: Color(0x00000004),
|
tileColor: Color(0x00000007),
|
||||||
selectedTileColor: Color(0x00000005),
|
selectedTileColor: Color(0x00000008),
|
||||||
horizontalTitleGap: 200,
|
horizontalTitleGap: 200,
|
||||||
minVerticalPadding: 300,
|
minVerticalPadding: 300,
|
||||||
minLeadingWidth: 400,
|
minLeadingWidth: 400,
|
||||||
@ -116,9 +122,12 @@ void main() {
|
|||||||
'selectedColor: Color(0x00000001)',
|
'selectedColor: Color(0x00000001)',
|
||||||
'iconColor: Color(0x00000002)',
|
'iconColor: Color(0x00000002)',
|
||||||
'textColor: Color(0x00000003)',
|
'textColor: Color(0x00000003)',
|
||||||
|
'titleTextStyle: TextStyle(inherit: true, color: Color(0x00000004))',
|
||||||
|
'subtitleTextStyle: TextStyle(inherit: true, color: Color(0x00000005))',
|
||||||
|
'leadingAndTrailingTextStyle: TextStyle(inherit: true, color: Color(0x00000006))',
|
||||||
'contentPadding: EdgeInsets.all(100.0)',
|
'contentPadding: EdgeInsets.all(100.0)',
|
||||||
'tileColor: Color(0x00000004)',
|
'tileColor: Color(0x00000007)',
|
||||||
'selectedTileColor: Color(0x00000005)',
|
'selectedTileColor: Color(0x00000008)',
|
||||||
'horizontalTitleGap: 200.0',
|
'horizontalTitleGap: 200.0',
|
||||||
'minVerticalPadding: 300.0',
|
'minVerticalPadding: 300.0',
|
||||||
'minLeadingWidth: 400.0',
|
'minLeadingWidth: 400.0',
|
||||||
@ -365,6 +374,99 @@ void main() {
|
|||||||
expect(textColor(trailingKey), theme.disabledColor);
|
expect(textColor(trailingKey), theme.disabledColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
"ListTile respects ListTileTheme's titleTextStyle, subtitleTextStyle & leadingAndTrailingTextStyle",
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
listTileTheme: const ListTileThemeData(
|
||||||
|
titleTextStyle: TextStyle(fontSize: 20.0),
|
||||||
|
subtitleTextStyle: TextStyle(fontSize: 17.5),
|
||||||
|
leadingAndTrailingTextStyle: TextStyle(fontSize: 15.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return const ListTile(
|
||||||
|
leading: TestText('leading'),
|
||||||
|
title: TestText('title'),
|
||||||
|
subtitle: TestText('subtitle') ,
|
||||||
|
trailing: TestText('trailing'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
|
||||||
|
expect(leading.text.style!.fontSize, 15.0);
|
||||||
|
final RenderParagraph title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.fontSize, 20.0);
|
||||||
|
final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
|
||||||
|
expect(subtitle.text.style!.fontSize, 17.5);
|
||||||
|
final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
|
||||||
|
expect(trailing.text.style!.fontSize, 15.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
"ListTile's titleTextStyle, subtitleTextStyle & leadingAndTrailingTextStyle are overridden by ListTile properties",
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
listTileTheme: const ListTileThemeData(
|
||||||
|
titleTextStyle: TextStyle(fontSize: 20.0),
|
||||||
|
subtitleTextStyle: TextStyle(fontSize: 17.5),
|
||||||
|
leadingAndTrailingTextStyle: TextStyle(fontSize: 15.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const TextStyle titleTextStyle = TextStyle(fontSize: 23.0);
|
||||||
|
const TextStyle subtitleTextStyle = TextStyle(fontSize: 20.0);
|
||||||
|
const TextStyle leadingAndTrailingTextStyle = TextStyle(fontSize: 18.0);
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return const ListTile(
|
||||||
|
titleTextStyle: titleTextStyle,
|
||||||
|
subtitleTextStyle: subtitleTextStyle,
|
||||||
|
leadingAndTrailingTextStyle: leadingAndTrailingTextStyle,
|
||||||
|
leading: TestText('leading'),
|
||||||
|
title: TestText('title'),
|
||||||
|
subtitle: TestText('subtitle') ,
|
||||||
|
trailing: TestText('trailing'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
|
||||||
|
expect(leading.text.style!.fontSize, 18.0);
|
||||||
|
final RenderParagraph title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.fontSize, 23.0);
|
||||||
|
final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
|
||||||
|
expect(subtitle.text.style!.fontSize, 20.0);
|
||||||
|
final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
|
||||||
|
expect(trailing.text.style!.fontSize, 18.0);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets("ListTile respects ListTileTheme's tileColor & selectedTileColor", (WidgetTester tester) async {
|
testWidgets("ListTile respects ListTileTheme's tileColor & selectedTileColor", (WidgetTester tester) async {
|
||||||
late ListTileThemeData theme;
|
late ListTileThemeData theme;
|
||||||
bool isSelected = false;
|
bool isSelected = false;
|
||||||
@ -479,4 +581,134 @@ void main() {
|
|||||||
// Test shape.
|
// Test shape.
|
||||||
expect(inkWellBorder, shapeBorder);
|
expect(inkWellBorder, shapeBorder);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTile respects MaterialStateColor LisTileTheme.textColor', (WidgetTester tester) async {
|
||||||
|
bool enabled = false;
|
||||||
|
bool selected = false;
|
||||||
|
const Color defaultColor = Colors.blue;
|
||||||
|
const Color selectedColor = Colors.green;
|
||||||
|
const Color disabledColor = Colors.red;
|
||||||
|
|
||||||
|
final ThemeData theme = ThemeData(
|
||||||
|
listTileTheme: ListTileThemeData(
|
||||||
|
textColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return disabledColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultColor;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
enabled: enabled,
|
||||||
|
selected: selected,
|
||||||
|
title: const TestText('title'),
|
||||||
|
subtitle: const TestText('subtitle') ,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test disabled state.
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
RenderParagraph title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.color, disabledColor);
|
||||||
|
|
||||||
|
// Test enabled state.
|
||||||
|
enabled = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.color, defaultColor);
|
||||||
|
|
||||||
|
// Test selected state.
|
||||||
|
selected = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
title = _getTextRenderObject(tester, 'title');
|
||||||
|
expect(title.text.style!.color, selectedColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTile respects MaterialStateColor LisTileTheme.iconColor', (WidgetTester tester) async {
|
||||||
|
bool enabled = false;
|
||||||
|
bool selected = false;
|
||||||
|
const Color defaultColor = Colors.blue;
|
||||||
|
const Color selectedColor = Colors.green;
|
||||||
|
const Color disabledColor = Colors.red;
|
||||||
|
final Key leadingKey = UniqueKey();
|
||||||
|
|
||||||
|
final ThemeData theme = ThemeData(
|
||||||
|
listTileTheme: ListTileThemeData(
|
||||||
|
iconColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return disabledColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return selectedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultColor;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
enabled: enabled,
|
||||||
|
selected: selected,
|
||||||
|
leading: TestIcon(key: leadingKey),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
|
||||||
|
|
||||||
|
// Test disabled state.
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
expect(iconColor(leadingKey), disabledColor);
|
||||||
|
|
||||||
|
// Test enabled state.
|
||||||
|
enabled = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(iconColor(leadingKey), defaultColor);
|
||||||
|
|
||||||
|
// Test selected state.
|
||||||
|
selected = true;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(iconColor(leadingKey), selectedColor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {
|
||||||
|
return tester.renderObject(find.descendant(
|
||||||
|
of: find.byType(ListTile),
|
||||||
|
matching: find.text(text),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user