From 94ff5202220bf741b3adb5c0a225147b6a0cd696 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Tue, 12 Oct 2021 15:05:09 -0700 Subject: [PATCH] Refactored ListTileTheme: ListTileThemeData, ThemeData.listThemeData (#91449) --- packages/flutter/lib/src/material/about.dart | 2 +- .../lib/src/material/checkbox_list_tile.dart | 4 +- .../lib/src/material/expansion_tile.dart | 10 +- .../flutter/lib/src/material/list_tile.dart | 775 ++++++++++++------ .../lib/src/material/radio_list_tile.dart | 2 +- .../lib/src/material/switch_list_tile.dart | 4 +- .../flutter/lib/src/material/theme_data.dart | 15 + .../flutter/test/material/list_tile_test.dart | 116 ++- .../test/material/theme_data_test.dart | 4 + 9 files changed, 673 insertions(+), 259 deletions(-) diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index 6bb081fa985..e796cf9ad6b 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -125,7 +125,7 @@ class AboutListTile extends StatelessWidget { /// Whether this list tile is part of a vertically dense list. /// - /// If this property is null, then its value is based on [ListTileTheme.dense]. + /// If this property is null, then its value is based on [ListTileThemeData.dense]. /// /// Dense list tiles default to a smaller height. final bool? dense; diff --git a/packages/flutter/lib/src/material/checkbox_list_tile.dart b/packages/flutter/lib/src/material/checkbox_list_tile.dart index 499657b019c..0290fd076fd 100644 --- a/packages/flutter/lib/src/material/checkbox_list_tile.dart +++ b/packages/flutter/lib/src/material/checkbox_list_tile.dart @@ -214,7 +214,7 @@ class CheckboxListTile extends StatelessWidget { /// Whether this list tile is part of a vertically dense list. /// - /// If this property is null then its value is based on [ListTileTheme.dense]. + /// If this property is null then its value is based on [ListTileThemeData.dense]. final bool? dense; /// Whether to render icons and text in the [activeColor]. @@ -252,7 +252,7 @@ class CheckboxListTile extends StatelessWidget { /// If tristate is false (the default), [value] must not be null. final bool tristate; - /// {@macro flutter.material.ListTileTheme.shape} + /// {@macro flutter.material.ListTile.shape} final ShapeBorder? shape; /// If non-null, defines the background color when [CheckboxListTile.selected] is true. diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 2b025d51c78..c81a5de163d 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -21,7 +21,7 @@ const Duration _kExpand = Duration(milliseconds: 200); /// [ExpansionTile] to save and restore its expanded state when it is scrolled /// in and out of view. /// -/// This class overrides the [ListTileTheme.iconColor] and [ListTileTheme.textColor] +/// This class overrides the [ListTileThemeData.iconColor] and [ListTileThemeData.textColor] /// theme properties for its [ListTile]. These colors animate between values when /// the tile is expanded and collapsed: between [iconColor], [collapsedIconColor] and /// between [textColor] and [collapsedTextColor]. @@ -176,23 +176,23 @@ class ExpansionTile extends StatefulWidget { /// The icon color of tile's expansion arrow icon when the sublist is expanded. /// - /// Used to override to the [ListTileTheme.iconColor]. + /// Used to override to the [ListTileThemeData.iconColor]. final Color? iconColor; /// The icon color of tile's expansion arrow icon when the sublist is collapsed. /// - /// Used to override to the [ListTileTheme.iconColor]. + /// Used to override to the [ListTileThemeData.iconColor]. final Color? collapsedIconColor; /// The color of the tile's titles when the sublist is expanded. /// - /// Used to override to the [ListTileTheme.textColor]. + /// Used to override to the [ListTileThemeData.textColor]. final Color? textColor; /// The color of the tile's titles when the sublist is collapsed. /// - /// Used to override to the [ListTileTheme.textColor]. + /// Used to override to the [ListTileThemeData.textColor]. final Color? collapsedTextColor; /// Typically used to force the expansion arrow icon to the tile's leading or trailing edge. diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index 1a143c60879..405469bb8df 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -3,7 +3,9 @@ // found in the LICENSE file. import 'dart:math' as math; +import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -30,6 +32,230 @@ enum ListTileStyle { drawer, } +/// Where to place the control in widgets that use [ListTile] to position a +/// control next to a label. +/// +/// See also: +/// +/// * [CheckboxListTile], which combines a [ListTile] with a [Checkbox]. +/// * [RadioListTile], which combines a [ListTile] with a [Radio] button. +/// * [SwitchListTile], which combines a [ListTile] with a [Switch]. +/// * [ExpansionTile], which combines a [ListTile] with a button that expands +/// or collapses the tile to reveal or hide the children. +enum ListTileControlAffinity { + /// Position the control on the leading edge, and the secondary widget, if + /// any, on the trailing edge. + leading, + + /// Position the control on the trailing edge, and the secondary widget, if + /// any, on the leading edge. + trailing, + + /// Position the control relative to the text in the fashion that is typical + /// for the current platform, and place the secondary widget on the opposite + /// side. + platform, +} + +/// Used with [ListTileTheme] to define default property values for +/// descendant [ListTile] widgets, as well as classes that build +/// [ListTile]s, like [CheckboxListTile], [RadioListTile], and +/// [SwitchListTile]. +/// +/// Descendant widgets obtain the current [ListTileThemeData] object +/// using `ListTileTheme.of(context)`. Instances of +/// [ListTileThemeData] can be customized with +/// [ListTileThemeData.copyWith]. +/// +/// A [ListTileThemeData] is often specified as part of the +/// overall [Theme] with [ThemeData.listTileTheme]. +/// +/// All [ListTileThemeData] properties are `null` by default. +/// When a theme property is null, the [ListTile] will provide its own +/// default based on the overall [Theme]'s textTheme and +/// colorScheme. See the individual [ListTile] properties for details. +/// +/// The [Drawer] widget specifies a list tile theme for its children that +/// defines [style] to be [ListTileStyle.drawer]. +/// +/// See also: +/// +/// * [ThemeData], which describes the overall theme information for the +/// application. +@immutable +class ListTileThemeData with Diagnosticable { + /// Creates a [ListTileThemeData]. + const ListTileThemeData ({ + this.dense, + this.shape, + this.style, + this.selectedColor, + this.iconColor, + this.textColor, + this.contentPadding, + this.tileColor, + this.selectedTileColor, + this.horizontalTitleGap, + this.minVerticalPadding, + this.minLeadingWidth, + this.enableFeedback, + }); + + /// Overrides the default value of [ListTile.dense]. + final bool? dense; + + /// Overrides the default value of [ListTile.shape]. + final ShapeBorder? shape; + + /// Overrides the default value of [ListTile.style]. + final ListTileStyle? style; + + /// Overrides the default value of [ListTile.selectedColor]. + final Color? selectedColor; + + /// Overrides the default value of [ListTile.iconColor]. + final Color? iconColor; + + /// Overrides the default value of [ListTile.textColor]. + final Color? textColor; + + /// Overrides the default value of [ListTile.contentPadding]. + final EdgeInsetsGeometry? contentPadding; + + /// Overrides the default value of [ListTile.tileColor]. + final Color? tileColor; + + /// Overrides the default value of [ListTile.selectedTileColor]. + final Color? selectedTileColor; + + /// Overrides the default value of [ListTile.horizontalTitleGap]. + final double? horizontalTitleGap; + + /// Overrides the default value of [ListTile.minVerticalPadding]. + final double? minVerticalPadding; + + /// Overrides the default value of [ListTile.minLeadingWidth]. + final double? minLeadingWidth; + + /// Overrides the default value of [ListTile.enableFeedback]. + final bool? enableFeedback; + + /// Creates a copy of this object with the given fields replaced with the + /// new values. + ListTileThemeData copyWith({ + bool? dense, + ShapeBorder? shape, + ListTileStyle? style, + Color? selectedColor, + Color? iconColor, + Color? textColor, + EdgeInsetsGeometry? contentPadding, + Color? tileColor, + Color? selectedTileColor, + double? horizontalTitleGap, + double? minVerticalPadding, + double? minLeadingWidth, + bool? enableFeedback, + }) { + return ListTileThemeData( + dense: dense ?? this.dense, + shape: shape ?? this.shape, + style: style ?? this.style, + selectedColor: selectedColor ?? this.selectedColor, + iconColor: iconColor ?? this.iconColor, + textColor: textColor ?? this.textColor, + contentPadding: contentPadding ?? this.contentPadding, + tileColor: tileColor ?? this.tileColor, + selectedTileColor: selectedTileColor ?? this.selectedTileColor, + horizontalTitleGap: horizontalTitleGap ?? this.horizontalTitleGap, + minVerticalPadding: minVerticalPadding ?? this.minVerticalPadding, + minLeadingWidth: minLeadingWidth ?? this.minLeadingWidth, + enableFeedback: enableFeedback ?? this.enableFeedback, + ); + } + + /// Linearly interpolate between ListTileThemeData objects. + static ListTileThemeData? lerp(ListTileThemeData? a, ListTileThemeData? b, double t) { + assert (t != null); + if (a == null && b == null) + return null; + return ListTileThemeData( + dense: t < 0.5 ? a?.dense : b?.dense, + shape: ShapeBorder.lerp(a?.shape, b?.shape, t), + style: t < 0.5 ? a?.style : b?.style, + selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t), + iconColor: Color.lerp(a?.iconColor, b?.iconColor, t), + textColor: Color.lerp(a?.textColor, b?.textColor, t), + contentPadding: EdgeInsetsGeometry.lerp(a?.contentPadding, b?.contentPadding, t), + tileColor: Color.lerp(a?.tileColor, b?.tileColor, t), + selectedTileColor: Color.lerp(a?.selectedTileColor, b?.selectedTileColor, t), + horizontalTitleGap: lerpDouble(a?.horizontalTitleGap, b?.horizontalTitleGap, t), + minVerticalPadding: lerpDouble(a?.minVerticalPadding, b?.minVerticalPadding, t), + minLeadingWidth: lerpDouble(a?.minLeadingWidth, b?.minLeadingWidth, t), + enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback, + ); + } + + @override + int get hashCode { + return hashValues( + dense, + shape, + style, + selectedColor, + iconColor, + textColor, + contentPadding, + tileColor, + selectedTileColor, + horizontalTitleGap, + minVerticalPadding, + minLeadingWidth, + enableFeedback, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other.runtimeType != runtimeType) + return false; + return other is ListTileThemeData + && other.dense == dense + && other.shape == shape + && other.style == style + && other.selectedColor == selectedColor + && other.iconColor == iconColor + && other.textColor == textColor + && other.contentPadding == contentPadding + && other.tileColor == tileColor + && other.selectedTileColor == selectedTileColor + && other.horizontalTitleGap == horizontalTitleGap + && other.minVerticalPadding == minVerticalPadding + && other.minLeadingWidth == minLeadingWidth + && other.enableFeedback == enableFeedback; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('dense', dense, defaultValue: null)); + properties.add(DiagnosticsProperty('shape', shape, defaultValue: null)); + properties.add(EnumProperty('style', style, defaultValue: null)); + properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null)); + properties.add(ColorProperty('iconColor', iconColor, defaultValue: null)); + properties.add(ColorProperty('textColor', textColor, defaultValue: null)); + properties.add(DiagnosticsProperty('contentPadding', contentPadding, defaultValue: null)); + properties.add(ColorProperty('tileColor', tileColor, defaultValue: null)); + properties.add(ColorProperty('selectedTileColor', selectedTileColor, defaultValue: null)); + properties.add(DoubleProperty('horizontalTitleGap', horizontalTitleGap, defaultValue: null)); + properties.add(DoubleProperty('minVerticalPadding', minVerticalPadding, defaultValue: null)); + properties.add(DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null)); + properties.add(DiagnosticsProperty('enableFeedback', enableFeedback, defaultValue: null)); + } +} + /// An inherited widget that defines color and style parameters for [ListTile]s /// in this widget's subtree. /// @@ -39,25 +265,183 @@ enum ListTileStyle { /// The [Drawer] widget specifies a tile theme for its children which sets /// [style] to [ListTileStyle.drawer]. class ListTileTheme extends InheritedTheme { - /// Creates a list tile theme that controls the color and style parameters for - /// [ListTile]s. + /// Creates a list tile theme that defines the color and style parameters for + /// descendant [ListTile]s. + /// + /// Only the [data] parameter should be used. The other parameters are + /// redundant (are now obsolete) and will be deprecated in a future update. const ListTileTheme({ Key? key, - this.dense = false, - this.shape, - this.style = ListTileStyle.list, - this.selectedColor, - this.iconColor, - this.textColor, - this.contentPadding, - this.tileColor, - this.selectedTileColor, - this.enableFeedback, - this.horizontalTitleGap, - this.minVerticalPadding, - this.minLeadingWidth, + ListTileThemeData? data, + bool? dense, + ShapeBorder? shape, + ListTileStyle? style, + Color? selectedColor, + Color? iconColor, + Color? textColor, + EdgeInsetsGeometry? contentPadding, + Color? tileColor, + Color? selectedTileColor, + bool? enableFeedback, + double? horizontalTitleGap, + double? minVerticalPadding, + double? minLeadingWidth, required Widget child, - }) : super(key: key, child: child); + }) : assert( + data == null || + (shape ?? + selectedColor ?? + iconColor ?? + textColor ?? + contentPadding ?? + tileColor ?? + selectedTileColor ?? + enableFeedback ?? + horizontalTitleGap ?? + minVerticalPadding ?? + minLeadingWidth) == null), + _data = data, + _dense = dense, + _shape = shape, + _style = style, + _selectedColor = selectedColor, + _iconColor = iconColor, + _textColor = textColor, + _contentPadding = contentPadding, + _tileColor = tileColor, + _selectedTileColor = selectedTileColor, + _enableFeedback = enableFeedback, + _horizontalTitleGap = horizontalTitleGap, + _minVerticalPadding = minVerticalPadding, + _minLeadingWidth = minLeadingWidth, + super(key: key, child: child); + + final ListTileThemeData? _data; + final bool? _dense; + final ShapeBorder? _shape; + final ListTileStyle? _style; + final Color? _selectedColor; + final Color? _iconColor; + final Color? _textColor; + final EdgeInsetsGeometry? _contentPadding; + final Color? _tileColor; + final Color? _selectedTileColor; + final double? _horizontalTitleGap; + final double? _minVerticalPadding; + final double? _minLeadingWidth; + final bool? _enableFeedback; + + /// The configuration of this theme. + ListTileThemeData get data { + return _data ?? ListTileThemeData( + shape: _shape, + style: _style, + selectedColor: _selectedColor, + iconColor: _iconColor, + textColor: _textColor, + contentPadding: _contentPadding, + tileColor: _tileColor, + selectedTileColor: _selectedTileColor, + enableFeedback: _enableFeedback, + horizontalTitleGap: _horizontalTitleGap, + minVerticalPadding: _minVerticalPadding, + minLeadingWidth: _minLeadingWidth, + ); + } + + /// Overrides the default value of [ListTile.dense]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.dense] property instead. + bool? get dense => _data != null ? _data!.dense : _dense; + + /// Overrides the default value of [ListTile.shape]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.shape] property instead. + ShapeBorder? get shape => _data != null ? _data!.shape : _shape; + + /// Overrides the default value of [ListTile.style]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.style] property instead. + ListTileStyle? get style => _data != null ? _data!.style : _style; + + /// Overrides the default value of [ListTile.selectedColor]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.selectedColor] property instead. + Color? get selectedColor => _data != null ? _data!.selectedColor : _selectedColor; + + /// Overrides the default value of [ListTile.iconColor]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.iconColor] property instead. + Color? get iconColor => _data != null ? _data!.iconColor : _iconColor; + + /// Overrides the default value of [ListTile.textColor]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.textColor] property instead. + Color? get textColor => _data != null ? _data!.textColor : _textColor; + + /// Overrides the default value of [ListTile.contentPadding]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.contentPadding] property instead. + EdgeInsetsGeometry? get contentPadding => _data != null ? _data!.contentPadding : _contentPadding; + + /// Overrides the default value of [ListTile.tileColor]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.tileColor] property instead. + Color? get tileColor => _data != null ? _data!.tileColor : _tileColor; + + /// Overrides the default value of [ListTile.selectedTileColor]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.selectedTileColor] property instead. + Color? get selectedTileColor => _data != null ? _data!.selectedTileColor : _selectedTileColor; + + /// Overrides the default value of [ListTile.horizontalTitleGap]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.horizontalTitleGap] property instead. + double? get horizontalTitleGap => _data != null ? _data!.horizontalTitleGap : _horizontalTitleGap; + + /// Overrides the default value of [ListTile.minVerticalPadding]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.minVerticalPadding] property instead. + double? get minVerticalPadding => _data != null ? _data!.minVerticalPadding : _minVerticalPadding; + + /// Overrides the default value of [ListTile.minLeadingWidth]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.minLeadingWidth] property instead. + double? get minLeadingWidth => _data != null ? _data!.minLeadingWidth : _minLeadingWidth; + + /// Overrides the default value of [ListTile.enableFeedback]. + /// + /// This property is obsolete: please use the [data] + /// [ListTileThemeData.enableFeedback] property instead. + bool? get enableFeedback => _data != null ? _data!.enableFeedback : _enableFeedback; + + /// The [data] property of the closest instance of this class that + /// encloses the given context. + /// + /// If there is no enclosing [ListTileTheme] widget, then + /// [ThemeData.listTileTheme] is used (see [Theme.of]). + /// + /// Typical usage is as follows: + /// + /// ```dart + /// ListTileThemeData theme = ListTileTheme.of(context); + /// ``` + static ListTileThemeData of(BuildContext context) { + final ListTileTheme? result = context.dependOnInheritedWidgetOfExactType(); + return result?.data ?? Theme.of(context).listTileTheme; + } /// Creates a list tile theme that controls the color and style parameters for /// [ListTile]s, and merges in the current list tile theme, if any. @@ -83,159 +467,54 @@ class ListTileTheme extends InheritedTheme { assert(child != null); return Builder( builder: (BuildContext context) { - final ListTileTheme parent = ListTileTheme.of(context); + final ListTileThemeData parent = ListTileTheme.of(context); return ListTileTheme( key: key, - dense: dense ?? parent.dense, - shape: shape ?? parent.shape, - style: style ?? parent.style, - selectedColor: selectedColor ?? parent.selectedColor, - iconColor: iconColor ?? parent.iconColor, - textColor: textColor ?? parent.textColor, - contentPadding: contentPadding ?? parent.contentPadding, - tileColor: tileColor ?? parent.tileColor, - selectedTileColor: selectedTileColor ?? parent.selectedTileColor, - enableFeedback: enableFeedback ?? parent.enableFeedback, - horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap, - minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding, - minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth, + data: ListTileThemeData( + dense: dense ?? parent.dense, + shape: shape ?? parent.shape, + style: style ?? parent.style, + selectedColor: selectedColor ?? parent.selectedColor, + iconColor: iconColor ?? parent.iconColor, + textColor: textColor ?? parent.textColor, + contentPadding: contentPadding ?? parent.contentPadding, + tileColor: tileColor ?? parent.tileColor, + selectedTileColor: selectedTileColor ?? parent.selectedTileColor, + enableFeedback: enableFeedback ?? parent.enableFeedback, + horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap, + minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding, + minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth, + ), child: child, ); }, ); } - /// If true then [ListTile]s will have the vertically dense layout. - final bool dense; - - /// {@template flutter.material.ListTileTheme.shape} - /// If specified, [shape] defines the [ListTile]'s shape. - /// {@endtemplate} - final ShapeBorder? shape; - - /// If specified, [style] defines the font used for [ListTile] titles. - final ListTileStyle style; - - /// If specified, the color used for icons and text when a [ListTile] is selected. - final Color? selectedColor; - - /// If specified, the icon color used for enabled [ListTile]s that are not selected. - final Color? iconColor; - - /// If specified, the text color used for enabled [ListTile]s that are not selected. - final Color? textColor; - - /// The tile's internal padding. - /// - /// Insets a [ListTile]'s contents: its [ListTile.leading], [ListTile.title], - /// [ListTile.subtitle], and [ListTile.trailing] widgets. - final EdgeInsetsGeometry? contentPadding; - - /// If specified, defines the background color for `ListTile` when - /// [ListTile.selected] is false. - /// - /// If [ListTile.tileColor] is provided, [tileColor] is ignored. - final Color? tileColor; - - /// If specified, defines the background color for `ListTile` when - /// [ListTile.selected] is true. - /// - /// If [ListTile.selectedTileColor] is provided, [selectedTileColor] is ignored. - 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 [ListTile.enableFeedback] is provided, [enableFeedback] is ignored. - final bool? enableFeedback; - - /// The closest instance of this class that encloses the given context. - /// - /// Typical usage is as follows: - /// - /// ```dart - /// ListTileTheme theme = ListTileTheme.of(context); - /// ``` - static ListTileTheme of(BuildContext context) { - final ListTileTheme? result = context.dependOnInheritedWidgetOfExactType(); - return result ?? const ListTileTheme(child: SizedBox()); - } - @override Widget wrap(BuildContext context, Widget child) { return ListTileTheme( - dense: dense, - shape: shape, - style: style, - selectedColor: selectedColor, - iconColor: iconColor, - textColor: textColor, - contentPadding: contentPadding, - tileColor: tileColor, - selectedTileColor: selectedTileColor, - enableFeedback: enableFeedback, - horizontalTitleGap: horizontalTitleGap, - minVerticalPadding: minVerticalPadding, - minLeadingWidth: minLeadingWidth, + data: ListTileThemeData( + dense: dense, + shape: shape, + style: style, + selectedColor: selectedColor, + iconColor: iconColor, + textColor: textColor, + contentPadding: contentPadding, + tileColor: tileColor, + selectedTileColor: selectedTileColor, + enableFeedback: enableFeedback, + horizontalTitleGap: horizontalTitleGap, + minVerticalPadding: minVerticalPadding, + minLeadingWidth: minLeadingWidth, + ), child: child, ); } @override - bool updateShouldNotify(ListTileTheme oldWidget) { - return dense != oldWidget.dense - || shape != oldWidget.shape - || style != oldWidget.style - || selectedColor != oldWidget.selectedColor - || iconColor != oldWidget.iconColor - || textColor != oldWidget.textColor - || contentPadding != oldWidget.contentPadding - || tileColor != oldWidget.tileColor - || selectedTileColor != oldWidget.selectedTileColor - || enableFeedback != oldWidget.enableFeedback - || horizontalTitleGap != oldWidget.horizontalTitleGap - || minVerticalPadding != oldWidget.minVerticalPadding - || minLeadingWidth != oldWidget.minLeadingWidth; - } -} - -/// Where to place the control in widgets that use [ListTile] to position a -/// control next to a label. -/// -/// See also: -/// -/// * [CheckboxListTile], which combines a [ListTile] with a [Checkbox]. -/// * [RadioListTile], which combines a [ListTile] with a [Radio] button. -/// * [SwitchListTile], which combines a [ListTile] with a [Switch]. -/// * [ExpansionTile], which combines a [ListTile] with a button that expands -/// or collapses the tile to reveal or hide the children. -enum ListTileControlAffinity { - /// Position the control on the leading edge, and the secondary widget, if - /// any, on the trailing edge. - leading, - - /// Position the control on the trailing edge, and the secondary widget, if - /// any, on the leading edge. - trailing, - - /// Position the control relative to the text in the fashion that is typical - /// for the current platform, and place the secondary widget on the opposite - /// side. - platform, + bool updateShouldNotify(ListTileTheme oldWidget) => data != oldWidget.data; } /// A single fixed-height row that typically contains some text as well as @@ -489,6 +768,10 @@ class ListTile extends StatelessWidget { this.dense, this.visualDensity, this.shape, + this.style, + this.selectedColor, + this.iconColor, + this.textColor, this.contentPadding, this.enabled = true, this.onTap, @@ -586,14 +869,62 @@ class ListTile extends StatelessWidget { /// widgets within a [Theme]. final VisualDensity? visualDensity; - /// The tile's shape. - /// + /// {@template flutter.material.ListTile.shape} /// Defines the tile's [InkWell.customBorder] and [Ink.decoration] shape. + /// {@endtemplate} /// - /// If this property is null then [ListTileTheme.shape] is used. - /// If that's null then a rectangular [Border] will be used. + /// If this property is null then [ListTileThemeData.shape] is used. If that + /// is also null then a rectangular [Border] will be used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. final ShapeBorder? shape; + /// Defines the color used for icons and text when the list tile is selected. + /// + /// If this property is null then [ListTileThemeData.selectedColor] + /// is used. If that is also null then [ColorScheme.primary] is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final Color? selectedColor; + + /// Defines the default color for [leading] and [trailing] icons. + /// + /// If this property is null then [ListTileThemeData.iconColor] is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final Color? iconColor; + + /// Defines the default color for the [title] and [subtitle]. + /// + /// If this property is null then [ListTileThemeData.textColor] is used. If that + /// is also null then [ColorScheme.primary] is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final Color? textColor; + + /// Defines the font used for the [title]. + /// + /// If this property is null then [ListTileThemeData.style] is used. If that + /// is also null then [ListTileStyle.list] is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final ListTileStyle? style; + /// The tile's internal padding. /// /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], @@ -738,103 +1069,85 @@ class ListTile extends StatelessWidget { yield tile; } - Color? _iconColor(ThemeData theme, ListTileTheme? tileTheme) { + Color? _iconColor(ThemeData theme, ListTileThemeData tileTheme) { if (!enabled) return theme.disabledColor; - if (selected && tileTheme?.selectedColor != null) - return tileTheme!.selectedColor; + if (selected) { + return selectedColor ?? tileTheme.selectedColor ?? theme.listTileTheme.selectedColor ?? theme.colorScheme.primary; + } - if (!selected && tileTheme?.iconColor != null) - return tileTheme!.iconColor; + final Color? color = iconColor ?? tileTheme.iconColor ?? theme.listTileTheme.iconColor; + 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 selected ? theme.colorScheme.primary : Colors.black45; + return Colors.black45; case Brightness.dark: - return selected ? theme.colorScheme.primary : null; // null - use current icon theme color + return null; // null - use current icon theme color } } - Color? _textColor(ThemeData theme, ListTileTheme? tileTheme, Color? defaultColor) { + Color? _textColor(ThemeData theme, ListTileThemeData tileTheme, Color? defaultColor) { if (!enabled) return theme.disabledColor; - if (selected && tileTheme?.selectedColor != null) - return tileTheme!.selectedColor; - - if (!selected && tileTheme?.textColor != null) - return tileTheme!.textColor; - - if (selected) - return theme.colorScheme.primary; - - return defaultColor; - } - - bool _isDenseLayout(ListTileTheme? tileTheme) { - return dense ?? tileTheme?.dense ?? false; - } - - TextStyle _titleTextStyle(ThemeData theme, ListTileTheme? tileTheme) { - final TextStyle style; - if (tileTheme != null) { - switch (tileTheme.style) { - case ListTileStyle.drawer: - style = theme.textTheme.bodyText1!; - break; - case ListTileStyle.list: - style = theme.textTheme.subtitle1!; - break; - } - } else { - style = theme.textTheme.subtitle1!; - } - final Color? color = _textColor(theme, tileTheme, style.color); - return _isDenseLayout(tileTheme) - ? style.copyWith(fontSize: 13.0, color: color) - : style.copyWith(color: color); - } - - TextStyle _subtitleTextStyle(ThemeData theme, ListTileTheme? tileTheme) { - final TextStyle style = theme.textTheme.bodyText2!; - final Color? color = _textColor(theme, tileTheme, theme.textTheme.caption!.color); - return _isDenseLayout(tileTheme) - ? style.copyWith(color: color, fontSize: 12.0) - : style.copyWith(color: color); - } - - TextStyle _trailingAndLeadingTextStyle(ThemeData theme, ListTileTheme? tileTheme) { - final TextStyle style = theme.textTheme.bodyText2!; - final Color? color = _textColor(theme, tileTheme, style.color); - return style.copyWith(color: color); - } - - Color _tileBackgroundColor(ListTileTheme? tileTheme) { - if (!selected) { - if (tileColor != null) - return tileColor!; - if (tileTheme?.tileColor != null) - return tileTheme!.tileColor!; - } - if (selected) { - if (selectedTileColor != null) - return selectedTileColor!; - if (tileTheme?.selectedTileColor != null) - return tileTheme!.selectedTileColor!; + return selectedColor ?? tileTheme.selectedColor ?? theme.listTileTheme.selectedColor ?? theme.colorScheme.primary; } - return Colors.transparent; + return textColor ?? tileTheme.textColor ?? theme.listTileTheme.textColor ?? defaultColor; + } + + bool _isDenseLayout(ThemeData theme, ListTileThemeData tileTheme) { + return dense ?? tileTheme.dense ?? theme.listTileTheme.dense ?? false; + } + + TextStyle _titleTextStyle(ThemeData theme, ListTileThemeData tileTheme) { + final TextStyle textStyle; + switch(style ?? tileTheme.style ?? theme.listTileTheme.style ?? ListTileStyle.list) { + case ListTileStyle.drawer: + textStyle = theme.textTheme.bodyText1!; + break; + case ListTileStyle.list: + textStyle = theme.textTheme.subtitle1!; + 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.textTheme.bodyText2!; + final Color? color = _textColor(theme, tileTheme, theme.textTheme.caption!.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.textTheme.bodyText2!; + final Color? color = _textColor(theme, tileTheme, textStyle.color); + return textStyle.copyWith(color: color); + } + + Color _tileBackgroundColor(ThemeData theme, ListTileThemeData tileTheme) { + final Color? color = selected + ? selectedTileColor ?? tileTheme.selectedTileColor ?? theme.listTileTheme.selectedTileColor + : tileColor ?? tileTheme.tileColor ?? theme.listTileTheme.tileColor; + return color ?? Colors.transparent; } @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ThemeData theme = Theme.of(context); - final ListTileTheme tileTheme = ListTileTheme.of(context); + final ListTileThemeData tileTheme = ListTileTheme.of(context); IconThemeData? iconThemeData; TextStyle? leadingAndTrailingTextStyle; @@ -916,7 +1229,7 @@ class ListTile extends StatelessWidget { child: Ink( decoration: ShapeDecoration( shape: shape ?? tileTheme.shape ?? const Border(), - color: _tileBackgroundColor(tileTheme), + color: _tileBackgroundColor(theme, tileTheme), ), child: SafeArea( top: false, @@ -927,7 +1240,7 @@ class ListTile extends StatelessWidget { title: titleText, subtitle: subtitleText, trailing: trailingIcon, - isDense: _isDenseLayout(tileTheme), + isDense: _isDenseLayout(theme, tileTheme), visualDensity: visualDensity ?? theme.visualDensity, isThreeLine: isThreeLine, textDirection: textDirection, diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index 45e056852d4..6ff7adaf848 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -236,7 +236,7 @@ class RadioListTile extends StatelessWidget { /// Whether this list tile is part of a vertically dense list. /// - /// If this property is null then its value is based on [ListTileTheme.dense]. + /// If this property is null then its value is based on [ListTileThemeData.dense]. final bool? dense; /// Whether to render icons and text in the [activeColor]. diff --git a/packages/flutter/lib/src/material/switch_list_tile.dart b/packages/flutter/lib/src/material/switch_list_tile.dart index a9a7bf91bf5..14b67aaefdd 100644 --- a/packages/flutter/lib/src/material/switch_list_tile.dart +++ b/packages/flutter/lib/src/material/switch_list_tile.dart @@ -291,7 +291,7 @@ class SwitchListTile extends StatelessWidget { /// Whether this list tile is part of a vertically dense list. /// - /// If this property is null then its value is based on [ListTileTheme.dense]. + /// If this property is null then its value is based on [ListTileThemeData.dense]. final bool? dense; /// The tile's internal padding. @@ -323,7 +323,7 @@ class SwitchListTile extends StatelessWidget { /// By default, the value of `controlAffinity` is [ListTileControlAffinity.platform]. final ListTileControlAffinity controlAffinity; - /// {@macro flutter.material.ListTileTheme.shape} + /// {@macro flutter.material.ListTile.shape} final ShapeBorder? shape; /// If non-null, defines the background color when [SwitchListTile.selected] is true. diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index d569c507b9b..01c8de3a6d6 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -28,6 +28,7 @@ import 'floating_action_button_theme.dart'; import 'ink_splash.dart'; import 'ink_well.dart' show InteractiveInkFeatureFactory; import 'input_decorator.dart'; +import 'list_tile.dart'; import 'navigation_bar_theme.dart'; import 'navigation_rail_theme.dart'; import 'outlined_button_theme.dart'; @@ -340,6 +341,7 @@ class ThemeData with Diagnosticable { SwitchThemeData? switchTheme, ProgressIndicatorThemeData? progressIndicatorTheme, DrawerThemeData? drawerTheme, + ListTileThemeData? listTileTheme, @Deprecated( 'This "fix" is now enabled by default. ' 'This feature was deprecated after v2.5.0-1.0.pre.', @@ -485,6 +487,7 @@ class ThemeData with Diagnosticable { switchTheme ??= const SwitchThemeData(); progressIndicatorTheme ??= const ProgressIndicatorThemeData(); drawerTheme ??= const DrawerThemeData(); + listTileTheme ??= const ListTileThemeData(); fixTextFieldOutlineLabel ??= true; useTextSelectionTheme ??= true; @@ -568,6 +571,7 @@ class ThemeData with Diagnosticable { switchTheme: switchTheme, progressIndicatorTheme: progressIndicatorTheme, drawerTheme: drawerTheme, + listTileTheme: listTileTheme, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel, useTextSelectionTheme: useTextSelectionTheme, androidOverscrollIndicator: androidOverscrollIndicator, @@ -703,6 +707,7 @@ class ThemeData with Diagnosticable { required this.switchTheme, required this.progressIndicatorTheme, required this.drawerTheme, + required this.listTileTheme, @Deprecated( 'This "fix" is now enabled by default. ' 'This feature was deprecated after v2.5.0-1.0.pre.', @@ -789,6 +794,7 @@ class ThemeData with Diagnosticable { assert(switchTheme != null), assert(progressIndicatorTheme != null), assert(drawerTheme != null), + assert(listTileTheme != null), assert(fixTextFieldOutlineLabel != null), assert(useTextSelectionTheme != null); @@ -1356,6 +1362,9 @@ class ThemeData with Diagnosticable { /// A theme for customizing the appearance and layout of [Drawer] widgets. final DrawerThemeData drawerTheme; + /// A theme for customizing the appearance of [ListTile] widgets. + final ListTileThemeData listTileTheme; + /// An obsolete flag to allow apps to opt-out of a /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y /// coordinate of the floating label in a [TextField] [OutlineInputBorder]. @@ -1517,6 +1526,7 @@ class ThemeData with Diagnosticable { SwitchThemeData? switchTheme, ProgressIndicatorThemeData? progressIndicatorTheme, DrawerThemeData? drawerTheme, + ListTileThemeData? listTileTheme, @Deprecated( 'This "fix" is now enabled by default. ' 'This feature was deprecated after v2.5.0-1.0.pre.', @@ -1609,6 +1619,7 @@ class ThemeData with Diagnosticable { switchTheme: switchTheme ?? this.switchTheme, progressIndicatorTheme: progressIndicatorTheme ?? this.progressIndicatorTheme, drawerTheme: drawerTheme ?? this.drawerTheme, + listTileTheme: listTileTheme ?? this.listTileTheme, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel, useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme, androidOverscrollIndicator: androidOverscrollIndicator ?? this.androidOverscrollIndicator, @@ -1771,6 +1782,7 @@ class ThemeData with Diagnosticable { switchTheme: SwitchThemeData.lerp(a.switchTheme, b.switchTheme, t), progressIndicatorTheme: ProgressIndicatorThemeData.lerp(a.progressIndicatorTheme, b.progressIndicatorTheme, t)!, drawerTheme: DrawerThemeData.lerp(a.drawerTheme, b.drawerTheme, t)!, + listTileTheme: ListTileThemeData.lerp(a.listTileTheme, b.listTileTheme, t)!, fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel, useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme, androidOverscrollIndicator: t < 0.5 ? a.androidOverscrollIndicator : b.androidOverscrollIndicator, @@ -1861,6 +1873,7 @@ class ThemeData with Diagnosticable { && other.switchTheme == switchTheme && other.progressIndicatorTheme == progressIndicatorTheme && other.drawerTheme == drawerTheme + && other.listTileTheme == listTileTheme && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel && other.useTextSelectionTheme == useTextSelectionTheme && other.androidOverscrollIndicator == androidOverscrollIndicator; @@ -1950,6 +1963,7 @@ class ThemeData with Diagnosticable { switchTheme, progressIndicatorTheme, drawerTheme, + listTileTheme, fixTextFieldOutlineLabel, useTextSelectionTheme, androidOverscrollIndicator, @@ -2037,6 +2051,7 @@ class ThemeData with Diagnosticable { properties.add(DiagnosticsProperty('switchTheme', switchTheme, defaultValue: defaultData.switchTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('progressIndicatorTheme', progressIndicatorTheme, defaultValue: defaultData.progressIndicatorTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty('drawerTheme', drawerTheme, defaultValue: defaultData.drawerTheme, level: DiagnosticLevel.debug)); + properties.add(DiagnosticsProperty('listTileTheme', listTileTheme, defaultValue: defaultData.listTileTheme, level: DiagnosticLevel.debug)); properties.add(EnumProperty('androidOverscrollIndicator', androidOverscrollIndicator, defaultValue: null, level: DiagnosticLevel.debug)); } } diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index 03180c4792b..92b549bd52c 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -51,6 +51,80 @@ class TestTextState extends State { } void main() { + test('ListTileThemeData copyWith, ==, hashCode basics', () { + expect(const ListTileThemeData(), const ListTileThemeData().copyWith()); + expect(const ListTileThemeData().hashCode, const ListTileThemeData().copyWith().hashCode); + }); + + test('ListTileThemeData defaults', () { + const ListTileThemeData themeData = ListTileThemeData(); + expect(themeData.dense, null); + expect(themeData.shape, null); + expect(themeData.style, null); + expect(themeData.selectedColor, null); + expect(themeData.iconColor, null); + expect(themeData.textColor, null); + expect(themeData.contentPadding, null); + expect(themeData.tileColor, null); + expect(themeData.selectedTileColor, null); + expect(themeData.horizontalTitleGap, null); + expect(themeData.minVerticalPadding, null); + expect(themeData.minLeadingWidth, null); + expect(themeData.enableFeedback, null); + }); + + testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + const ListTileThemeData().debugFillProperties(builder); + + final List description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(); + + expect(description, []); + }); + + testWidgets('ListTileThemeData implements debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + const ListTileThemeData( + dense: true, + shape: StadiumBorder(), + style: ListTileStyle.drawer, + selectedColor: Color(0x00000001), + iconColor: Color(0x00000002), + textColor: Color(0x00000003), + contentPadding: EdgeInsets.all(100), + tileColor: Color(0x00000004), + selectedTileColor: Color(0x00000005), + horizontalTitleGap: 200, + minVerticalPadding: 300, + minLeadingWidth: 400, + enableFeedback: true, + ).debugFillProperties(builder); + + final List description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(); + + expect(description, [ + 'dense: true', + 'shape: StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none))', + 'style: drawer', + 'selectedColor: Color(0x00000001)', + 'iconColor: Color(0x00000002)', + 'textColor: Color(0x00000003)', + 'contentPadding: EdgeInsets.all(100.0)', + 'tileColor: Color(0x00000004)', + 'selectedTileColor: Color(0x00000005)', + 'horizontalTitleGap: 200.0', + 'minVerticalPadding: 300.0', + 'minLeadingWidth: 400.0', + 'enableFeedback: true' + ]); + }); + testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async { // See https://material.io/go/design-lists @@ -291,11 +365,13 @@ void main() { home: Material( child: Center( child: ListTileTheme( - dense: dense, - shape: shape, - selectedColor: selectedColor, - iconColor: iconColor, - textColor: textColor, + data: ListTileThemeData( + dense: dense, + shape: shape, + selectedColor: selectedColor, + iconColor: iconColor, + textColor: textColor, + ), child: Builder( builder: (BuildContext context) { theme = Theme.of(context); @@ -1720,15 +1796,17 @@ void main() { }); testWidgets("ListTile respects ListTileTheme's tileColor & selectedTileColor", (WidgetTester tester) async { - late ListTileTheme theme; + late ListTileThemeData theme; bool isSelected = false; await tester.pumpWidget( MaterialApp( home: Material( child: ListTileTheme( - tileColor: Colors.green.shade500, - selectedTileColor: Colors.red.shade500, + data: ListTileThemeData( + tileColor: Colors.green.shade500, + selectedTileColor: Colors.red.shade500, + ), child: Center( child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -1766,8 +1844,10 @@ void main() { MaterialApp( home: Material( child: ListTileTheme( - selectedTileColor: Colors.green, - tileColor: Colors.red, + data: const ListTileThemeData( + selectedTileColor: Colors.green, + tileColor: Colors.red, + ), child: Center( child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -1898,7 +1978,7 @@ void main() { MaterialApp( home: Material( child: ListTileTheme( - enableFeedback: enableFeedbackTheme, + data: const ListTileThemeData(enableFeedback: enableFeedbackTheme), child: ListTile( title: const Text('Title'), onTap: () {}, @@ -1922,7 +2002,7 @@ void main() { MaterialApp( home: Material( child: ListTileTheme( - enableFeedback: enableFeedbackTheme, + data: const ListTileThemeData(enableFeedback: enableFeedbackTheme), child: ListTile( enableFeedback: enableFeedback, title: const Text('Title'), @@ -1948,7 +2028,7 @@ void main() { textDirection: textDirection, child: Material( child: ListTileTheme( - horizontalTitleGap: themeHorizontalTitleGap, + data: ListTileThemeData(horizontalTitleGap: themeHorizontalTitleGap), child: Container( alignment: Alignment.topLeft, child: ListTile( @@ -2081,7 +2161,7 @@ void main() { textDirection: textDirection, child: Material( child: ListTileTheme( - minVerticalPadding: themeMinVerticalPadding, + data: ListTileThemeData(minVerticalPadding: themeMinVerticalPadding), child: Container( alignment: Alignment.topLeft, child: ListTile( @@ -2127,7 +2207,7 @@ void main() { textDirection: textDirection, child: Material( child: ListTileTheme( - minLeadingWidth: themeMinLeadingWidth, + data: ListTileThemeData(minLeadingWidth: themeMinLeadingWidth), child: Container( alignment: Alignment.topLeft, child: ListTile( @@ -2241,8 +2321,10 @@ void main() { home: Material( child: Center( child: ListTileTheme( - selectedColor: selectedColor, - textColor: defaultColor, + data: const ListTileThemeData( + selectedColor: selectedColor, + textColor: defaultColor, + ), child: Builder( builder: (BuildContext context) { theme = Theme.of(context); diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index c556bf64be9..a2071195422 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -346,6 +346,7 @@ void main() { switchTheme: const SwitchThemeData(), progressIndicatorTheme: const ProgressIndicatorThemeData(), drawerTheme: const DrawerThemeData(), + listTileTheme: const ListTileThemeData(), fixTextFieldOutlineLabel: false, useTextSelectionTheme: false, androidOverscrollIndicator: null, @@ -443,6 +444,7 @@ void main() { switchTheme: const SwitchThemeData(), progressIndicatorTheme: const ProgressIndicatorThemeData(), drawerTheme: const DrawerThemeData(), + listTileTheme: const ListTileThemeData(), fixTextFieldOutlineLabel: true, useTextSelectionTheme: true, androidOverscrollIndicator: AndroidOverscrollIndicator.stretch, @@ -521,6 +523,7 @@ void main() { switchTheme: otherTheme.switchTheme, progressIndicatorTheme: otherTheme.progressIndicatorTheme, drawerTheme: otherTheme.drawerTheme, + listTileTheme: otherTheme.listTileTheme, fixTextFieldOutlineLabel: otherTheme.fixTextFieldOutlineLabel, ); @@ -596,6 +599,7 @@ void main() { expect(themeDataCopy.switchTheme, equals(otherTheme.switchTheme)); expect(themeDataCopy.progressIndicatorTheme, equals(otherTheme.progressIndicatorTheme)); expect(themeDataCopy.drawerTheme, equals(otherTheme.drawerTheme)); + expect(themeDataCopy.listTileTheme, equals(otherTheme.listTileTheme)); expect(themeDataCopy.fixTextFieldOutlineLabel, equals(otherTheme.fixTextFieldOutlineLabel)); });