Reland "Add DataTableTheme to allow for themable DataTables (#64316)" (#65383)
diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart index aa7c82b..1ef4259 100644 --- a/packages/flutter/lib/material.dart +++ b/packages/flutter/lib/material.dart
@@ -52,6 +52,7 @@ export 'src/material/curves.dart'; export 'src/material/data_table.dart'; export 'src/material/data_table_source.dart'; +export 'src/material/data_table_theme.dart'; export 'src/material/debug.dart'; export 'src/material/dialog.dart'; export 'src/material/dialog_theme.dart';
diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index 98cc73d2..8bbff0f 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart
@@ -11,7 +11,6 @@ import 'package:flutter/widgets.dart'; import 'checkbox.dart'; -import 'colors.dart'; import 'constants.dart'; import 'debug.dart'; import 'divider.dart'; @@ -414,25 +413,26 @@ this.sortColumnIndex, this.sortAscending = true, this.onSelectAll, - this.dataRowHeight = kMinInteractiveDimension, - this.headingRowHeight = 56.0, - this.horizontalMargin = 24.0, - this.columnSpacing = 56.0, + this.dataRowColor, + this.dataRowHeight, + this.dataTextStyle, + this.headingRowColor, + this.headingRowHeight, + this.headingTextStyle, + this.horizontalMargin, + this.columnSpacing, this.showCheckboxColumn = true, - this.dividerThickness = 1.0, + this.showBottomBorder = false, + this.dividerThickness, @required this.rows, }) : assert(columns != null), assert(columns.isNotEmpty), assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), assert(sortAscending != null), - assert(dataRowHeight != null), - assert(headingRowHeight != null), - assert(horizontalMargin != null), - assert(columnSpacing != null), assert(showCheckboxColumn != null), assert(rows != null), assert(!rows.any((DataRow row) => row.cells.length != columns.length)), - assert(dividerThickness != null && dividerThickness >= 0), + assert(dividerThickness == null || dividerThickness >= 0), _onlyTextColumn = _initOnlyTextColumn(columns), super(key: key); @@ -475,27 +475,110 @@ /// row is selectable. final ValueSetter<bool> onSelectAll; - /// The height of each row (excluding the row that contains column headings). + /// {@template flutter.material.dataTable.dataRowColor} + /// The background color for the data rows. /// - /// This value defaults to kMinInteractiveDimension to adhere to the Material + /// The effective background color can be made to depend on the + /// [MaterialState] state, i.e. if the row is selected, pressed, hovered, + /// focused, disabled or enabled. The color is painted as an overlay to the + /// row. To make sure that the row's [InkWell] is visible (when pressed, + /// hovered and focused), it is recommended to use a translucent background + /// color. + /// {@endtemplate} + /// + /// By default, the background color is transparent unless selected. Selected + /// rows have a grey translucent color. To set a different color for + /// individual rows, see [DataRow.color]. + /// + /// {@template flutter.material.dataTable.dataRowColorCode} + /// ```dart + /// DataTable( + /// dataRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { + /// if (states.contains(MaterialState.selected)) + /// return Theme.of(context).colorScheme.primary.withOpacity(0.08); + /// return null; // Use the default value. + /// }), + /// ) + /// ``` + /// + /// See also: + /// + /// * The Material Design specification for overlay colors and how they + /// match a component's state: + /// <https://material.io/design/interaction/states.html#anatomy>. + /// {@endtemplate} + final MaterialStateProperty<Color> dataRowColor; + + /// {@template flutter.material.dataTable.dataRowHeight} + /// The height of each row (excluding the row that contains column headings). + /// {@endtemplate} + /// + /// This value defaults to [kMinInteractiveDimension] to adhere to the Material /// Design specifications. final double dataRowHeight; + /// {@template flutter.material.dataTable.dataTextStyle} + /// The text style for data rows. + /// {@endtemplate} + /// + /// By default, the text style is [TextTheme.bodyText2]. + final TextStyle dataTextStyle; + + /// {@template flutter.material.dataTable.headingRowColor} + /// The background color for the heading row. + /// + /// The effective background color can be made to depend on the + /// [MaterialState] state, i.e. if the row is pressed, hovered, focused when + /// sorted. The color is painted as an overlay to the row. To make sure that + /// the row's [InkWell] is visible (when pressed, hovered and focused), it is + /// recommended to use a translucent color. + /// + /// ```dart + /// DataTable( + /// headingRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { + /// if (states.contains(MaterialState.hovered)) + /// return Theme.of(context).colorScheme.primary.withOpacity(0.08); + /// return null; // Use the default value. + /// }), + /// ) + /// ``` + /// + /// See also: + /// + /// * The Material Design specification for overlay colors and how they + /// match a component's state: + /// <https://material.io/design/interaction/states.html#anatomy>. + /// {@endtemplate} + final MaterialStateProperty<Color> headingRowColor; + + /// {@template flutter.material.dataTable.headingRowHeight} /// The height of the heading row. + /// {@endtemplate} /// /// This value defaults to 56.0 to adhere to the Material Design specifications. final double headingRowHeight; + /// {@template flutter.material.dataTable.headingTextStyle} + /// The text style for the heading row. + /// {@endtemplate} + /// + /// By default, the text style is [TextTheme.subtitle2]. + final TextStyle headingTextStyle; + + /// {@template flutter.material.dataTable.horizontalMargin} /// The horizontal margin between the edges of the table and the content /// in the first and last cells of each row. /// /// When a checkbox is displayed, it is also the margin between the checkbox /// the content in the first data column. + /// {@endtemplate} /// /// This value defaults to 24.0 to adhere to the Material Design specifications. final double horizontalMargin; + /// {@template flutter.material.dataTable.columnSpacing} /// The horizontal margin between the contents of each data column. + /// {@endtemplate} /// /// This value defaults to 56.0 to adhere to the Material Design specifications. final double columnSpacing; @@ -517,6 +600,20 @@ /// Must be non-null, but may be empty. final List<DataRow> rows; + /// {@template flutter.material.dataTable.dividerThickness} + /// The width of the divider that appears between [TableRow]s. + /// + /// Must be greater than or equal to zero. + /// {@endtemplate} + /// This value defaults to 1.0. + final double dividerThickness; + + /// Whether a border at the bottom of the table is displayed. + /// + /// By default, a border is not shown at the bottom to allow for a border + /// around the table. + final bool showBottomBorder; + // Set by the constructor to the index of the only Column that is // non-numeric, if there is exactly one, otherwise null. final int _onlyTextColumn; @@ -551,29 +648,45 @@ } } + /// The default height of the heading row. + static const double _headingRowHeight = 56.0; + + /// The default horizontal margin between the edges of the table and the content + /// in the first and last cells of each row. + static const double _horizontalMargin = 24.0; + + /// The default horizontal margin between the contents of each data column. + static const double _columnSpacing = 56.0; + + /// The default padding between the heading content and sort arrow. static const double _sortArrowPadding = 2.0; - static const double _headingFontSize = 12.0; + + /// The default divider thickness. + static const double _dividerThickness = 1.0; + static const Duration _sortArrowAnimationDuration = Duration(milliseconds: 150); static const Color _grey100Opacity = Color(0x0A000000); // Grey 100 as opacity instead of solid color static const Color _grey300Opacity = Color(0x1E000000); // Dark theme variant is just a guess. - /// The width of the divider that appears between [TableRow]s. - /// - /// Must be non-null and greater than or equal to zero. - /// This value defaults to 1.0. - final double dividerThickness; - Widget _buildCheckbox({ + BuildContext context, Color activeColor, bool checked, VoidCallback onRowTap, ValueChanged<bool> onCheckboxChanged, MaterialStateProperty<Color> overlayColor, }) { + final ThemeData themeData = Theme.of(context); + final double effectiveHorizontalMargin = horizontalMargin + ?? themeData.dataTableTheme.horizontalMargin + ?? _horizontalMargin; Widget contents = Semantics( container: true, child: Padding( - padding: EdgeInsetsDirectional.only(start: horizontalMargin, end: horizontalMargin / 2.0), + padding: EdgeInsetsDirectional.only( + start: effectiveHorizontalMargin, + end: effectiveHorizontalMargin / 2.0, + ), child: Center( child: Checkbox( activeColor: activeColor, @@ -605,39 +718,37 @@ VoidCallback onSort, bool sorted, bool ascending, + MaterialStateProperty<Color> overlayColor, }) { - List<Widget> arrowWithPadding() { - return onSort == null ? const <Widget>[] : <Widget>[ - _SortArrow( - visible: sorted, - up: sorted ? ascending : null, - duration: _sortArrowAnimationDuration, - ), - const SizedBox(width: _sortArrowPadding), - ]; - } + final ThemeData themeData = Theme.of(context); label = Row( textDirection: numeric ? TextDirection.rtl : null, children: <Widget>[ label, - ...arrowWithPadding(), + if (onSort != null) + ...<Widget>[ + _SortArrow( + visible: sorted, + up: sorted ? ascending : null, + duration: _sortArrowAnimationDuration, + ), + const SizedBox(width: _sortArrowPadding), + ], ], ); + + final TextStyle effectiveHeadingTextStyle = headingTextStyle + ?? themeData.dataTableTheme.headingTextStyle + ?? themeData.textTheme.subtitle2; + final double effectiveHeadingRowHeight = headingRowHeight + ?? themeData.dataTableTheme.headingRowHeight + ?? _headingRowHeight; label = Container( padding: padding, - height: headingRowHeight, + height: effectiveHeadingRowHeight, alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart, child: AnimatedDefaultTextStyle( - style: TextStyle( - // TODO(hansmuller): This should use the information provided by - // textTheme/DataTableTheme, https://github.com/flutter/flutter/issues/56079 - fontWeight: FontWeight.w500, - fontSize: _headingFontSize, - height: math.min(1.0, headingRowHeight / _headingFontSize), - color: (Theme.of(context).brightness == Brightness.light) - ? ((onSort != null && sorted) ? Colors.black87 : Colors.black54) - : ((onSort != null && sorted) ? Colors.white : Colors.white70), - ), + style: effectiveHeadingTextStyle, softWrap: false, duration: _sortArrowAnimationDuration, child: label, @@ -649,10 +760,12 @@ child: label, ); } + // TODO(dkwingsmt): Only wrap Inkwell if onSort != null. Blocked by // https://github.com/flutter/flutter/issues/51152 label = InkWell( onTap: onSort, + overlayColor: overlayColor, child: label, ); return label; @@ -669,7 +782,7 @@ VoidCallback onSelectChanged, MaterialStateProperty<Color> overlayColor, }) { - final bool isLightTheme = Theme.of(context).brightness == Brightness.light; + final ThemeData themeData = Theme.of(context); if (showEditIcon) { const Widget icon = Icon(Icons.edit, size: 18.0); label = Expanded(child: label); @@ -678,25 +791,22 @@ children: <Widget>[ label, icon ], ); } + + final TextStyle effectiveDataTextStyle = dataTextStyle + ?? themeData.dataTableTheme.dataTextStyle + ?? themeData.textTheme.bodyText2; + final double effectiveDataRowHeight = dataRowHeight + ?? themeData.dataTableTheme.dataRowHeight + ?? kMinInteractiveDimension; label = Container( padding: padding, - height: dataRowHeight, + height: effectiveDataRowHeight, alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart, child: DefaultTextStyle( - style: TextStyle( - // TODO(hansmuller): This should use the information provided by - // textTheme/DataTableTheme, https://github.com/flutter/flutter/issues/56079 - fontSize: 13.0, - color: isLightTheme - ? (placeholder ? Colors.black38 : Colors.black87) - : (placeholder ? Colors.white38 : Colors.white70), + style: effectiveDataTextStyle.copyWith( + color: placeholder ? effectiveDataTextStyle.color.withOpacity(0.6) : null, ), - child: IconTheme.merge( - data: IconThemeData( - color: isLightTheme ? Colors.black54 : Colors.white70, - ), - child: DropdownButtonHideUnderline(child: label), - ), + child: DropdownButtonHideUnderline(child: label), ), ); if (onTap != null) { @@ -720,12 +830,16 @@ assert(!_debugInteractive || debugCheckHasMaterial(context)); final ThemeData theme = Theme.of(context); + final MaterialStateProperty<Color> effectiveHeadingRowColor = headingRowColor + ?? theme.dataTableTheme.headingRowColor; + final MaterialStateProperty<Color> effectiveDataRowColor = dataRowColor + ?? theme.dataTableTheme.dataRowColor; final MaterialStateProperty<Color> defaultRowColor = MaterialStateProperty.resolveWith( (Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { - // TODO(per): Add theming support for DataTable, https://github.com/flutter/flutter/issues/56079. // The color has to be transparent so you can see the ink on // the [Material]. + // TODO(perclasson): Align with Material specs, use translucent primary color: https://github.com/flutter/flutter/issues/64314. return (Theme.of(context).brightness == Brightness.light) ? _grey100Opacity : _grey300Opacity; } @@ -735,6 +849,12 @@ final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null); final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable; final bool allChecked = displayCheckboxColumn && !rows.any((DataRow row) => row.onSelectChanged != null && !row.selected); + final double effectiveHorizontalMargin = horizontalMargin + ?? theme.dataTableTheme.horizontalMargin + ?? _horizontalMargin; + final double effectiveColumnSpacing = columnSpacing + ?? theme.dataTableTheme.columnSpacing + ?? _columnSpacing; final List<TableColumnWidth> tableColumns = List<TableColumnWidth>(columns.length + (displayCheckboxColumn ? 1 : 0)); final List<TableRow> tableRows = List<TableRow>.generate( @@ -748,13 +868,22 @@ if (isDisabled) MaterialState.disabled, }; - final Color rowColor = index > 0 ? rows[index - 1].color?.resolve(states) : null; + final Color resolvedDataRowColor = index > 0 ? (rows[index - 1].color ?? effectiveDataRowColor)?.resolve(states) : null; + final Color resolvedHeadingRowColor = effectiveHeadingRowColor?.resolve(<MaterialState>{}); + final Color rowColor = index > 0 ? resolvedDataRowColor : resolvedHeadingRowColor; + final BorderSide borderSide = Divider.createBorderSide( + context, + width: dividerThickness + ?? theme.dataTableTheme.dividerThickness + ?? _dividerThickness, + ); + final Border border = showBottomBorder + ? Border(bottom: borderSide) + : index == 0 ? null : Border(top: borderSide); return TableRow( key: index == 0 ? _headingRowKey : rows[index - 1].key, decoration: BoxDecoration( - border: Border( - bottom: Divider.createBorderSide(context, width: dividerThickness), - ), + border: border, color: rowColor ?? defaultRowColor.resolve(states), ), children: List<Widget>(tableColumns.length), @@ -766,8 +895,9 @@ int displayColumnIndex = 0; if (displayCheckboxColumn) { - tableColumns[0] = FixedColumnWidth(horizontalMargin + Checkbox.width + horizontalMargin / 2.0); + tableColumns[0] = FixedColumnWidth(effectiveHorizontalMargin + Checkbox.width + effectiveHorizontalMargin / 2.0); tableRows[0].children[0] = _buildCheckbox( + context: context, activeColor: theme.accentColor, checked: allChecked, onCheckboxChanged: _handleSelectAll, @@ -775,11 +905,12 @@ rowIndex = 1; for (final DataRow row in rows) { tableRows[rowIndex].children[0] = _buildCheckbox( + context: context, activeColor: theme.accentColor, checked: row.selected, onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null , onCheckboxChanged: row.onSelectChanged, - overlayColor: row.color, + overlayColor: row.color ?? effectiveDataRowColor, ); rowIndex += 1; } @@ -791,18 +922,18 @@ double paddingStart; if (dataColumnIndex == 0 && displayCheckboxColumn) { - paddingStart = horizontalMargin / 2.0; + paddingStart = effectiveHorizontalMargin / 2.0; } else if (dataColumnIndex == 0 && !displayCheckboxColumn) { - paddingStart = horizontalMargin; + paddingStart = effectiveHorizontalMargin; } else { - paddingStart = columnSpacing / 2.0; + paddingStart = effectiveColumnSpacing / 2.0; } double paddingEnd; if (dataColumnIndex == columns.length - 1) { - paddingEnd = horizontalMargin; + paddingEnd = effectiveHorizontalMargin; } else { - paddingEnd = columnSpacing / 2.0; + paddingEnd = effectiveColumnSpacing / 2.0; } final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only( @@ -823,6 +954,7 @@ onSort: column.onSort != null ? () => column.onSort(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) : null, sorted: dataColumnIndex == sortColumnIndex, ascending: sortAscending, + overlayColor: effectiveHeadingRowColor, ); rowIndex = 1; for (final DataRow row in rows) { @@ -836,7 +968,7 @@ showEditIcon: cell.showEditIcon, onTap: cell.onTap, onSelectChanged: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null, - overlayColor: row.color, + overlayColor: row.color ?? effectiveDataRowColor, ); rowIndex += 1; } @@ -1035,10 +1167,9 @@ transform: Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value) ..setTranslationRaw(0.0, _arrowIconBaselineOffset, 0.0), alignment: Alignment.center, - child: Icon( + child: const Icon( Icons.arrow_upward, size: _arrowIconSize, - color: (Theme.of(context).brightness == Brightness.light) ? Colors.black87 : Colors.white70, ), ), );
diff --git a/packages/flutter/lib/src/material/data_table_theme.dart b/packages/flutter/lib/src/material/data_table_theme.dart new file mode 100644 index 0000000..708d779 --- /dev/null +++ b/packages/flutter/lib/src/material/data_table_theme.dart
@@ -0,0 +1,240 @@ +// 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. + +// @dart = 2.8 + +import 'dart:ui' show lerpDouble; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +import 'material_state.dart'; +import 'theme.dart'; + +/// Defines default property values for descendant [DataTable] +/// widgets. +/// +/// Descendant widgets obtain the current [DataTableThemeData] object +/// using `DataTableTheme.of(context)`. Instances of +/// [DataTableThemeData] can be customized with +/// [DataTableThemeData.copyWith]. +/// +/// Typically a [DataTableThemeData] is specified as part of the +/// overall [Theme] with [ThemeData.dataTableTheme]. +/// +/// All [DataTableThemeData] properties are `null` by default. When +/// null, the [DataTable] will use the values from [ThemeData] if they exist, +/// otherwise it will provide its own defaults based on the overall [Theme]'s +/// textTheme and colorScheme. See the individual [DataTable] properties for +/// details. +/// +/// See also: +/// +/// * [ThemeData], which describes the overall theme information for the +/// application. +@immutable +class DataTableThemeData with Diagnosticable { + /// Creates a theme that can be used for [ThemeData.dataTableTheme]. + const DataTableThemeData({ + this.dataRowColor, + this.dataRowHeight, + this.dataTextStyle, + this.headingRowColor, + this.headingRowHeight, + this.headingTextStyle, + this.horizontalMargin, + this.columnSpacing, + this.dividerThickness, + }); + + /// {@macro flutter.material.dataTable.dataRowColor} + /// {@macro flutter.material.dataTable.dataRowColorCode} + final MaterialStateProperty<Color> dataRowColor; + + /// {@macro flutter.material.dataTable.dataRowHeight} + final double dataRowHeight; + + /// {@macro flutter.material.dataTable.dataTextStyle} + final TextStyle dataTextStyle; + + /// {@macro flutter.material.dataTable.headingRowColor} + final MaterialStateProperty<Color> headingRowColor; + + /// {@macro flutter.material.dataTable.headingRowHeight} + final double headingRowHeight; + + /// {@macro flutter.material.dataTable.headingTextStyle} + final TextStyle headingTextStyle; + + /// {@macro flutter.material.dataTable.horizontalMargin} + final double horizontalMargin; + + /// {@macro flutter.material.dataTable.columnSpacing} + final double columnSpacing; + + /// {@macro flutter.material.dataTable.dividerThickness} + final double dividerThickness; + + /// Creates a copy of this object but with the given fields replaced with the + /// new values. + DataTableThemeData copyWith({ + MaterialStateProperty<Color> dataRowColor, + double dataRowHeight, + TextStyle dataTextStyle, + MaterialStateProperty<Color> headingRowColor, + double headingRowHeight, + TextStyle headingTextStyle, + double horizontalMargin, + double columnSpacing, + double dividerThickness, + }) { + return DataTableThemeData( + dataRowColor: dataRowColor ?? this.dataRowColor, + dataRowHeight: dataRowHeight ?? this.dataRowHeight, + dataTextStyle: dataTextStyle ?? this.dataTextStyle, + headingRowColor: headingRowColor ?? this.headingRowColor, + headingRowHeight: headingRowHeight ?? this.headingRowHeight, + headingTextStyle: headingTextStyle ?? this.headingTextStyle, + horizontalMargin: horizontalMargin ?? this.horizontalMargin, + columnSpacing: columnSpacing ?? this.columnSpacing, + dividerThickness: dividerThickness ?? this.dividerThickness, + ); + } + + /// Linearly interpolate between two [DataTableThemeData]s. + /// + /// The argument `t` must not be null. + /// + /// {@macro dart.ui.shadow.lerp} + static DataTableThemeData lerp(DataTableThemeData a, DataTableThemeData b, double t) { + assert(t != null); + return DataTableThemeData( + dataRowColor: _lerpProperties(a.dataRowColor, b.dataRowColor, t, Color.lerp), + dataRowHeight: lerpDouble(a.dataRowHeight, b.dataRowHeight, t), + dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t), + headingRowColor: _lerpProperties(a.headingRowColor, b.headingRowColor, t, Color.lerp), + headingRowHeight: lerpDouble(a.headingRowHeight, b.headingRowHeight, t), + headingTextStyle: TextStyle.lerp(a.headingTextStyle, b.headingTextStyle, t), + horizontalMargin: lerpDouble(a.horizontalMargin, b.horizontalMargin, t), + columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t), + dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t) + ); + } + + @override + int get hashCode { + return hashValues( + dataRowColor, + dataRowHeight, + dataTextStyle, + headingRowColor, + headingRowHeight, + headingTextStyle, + horizontalMargin, + columnSpacing, + dividerThickness, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other.runtimeType != runtimeType) + return false; + return other is DataTableThemeData + && other.dataRowColor == dataRowColor + && other.dataRowHeight == dataRowHeight + && other.dataTextStyle == dataTextStyle + && other.headingRowColor == headingRowColor + && other.headingRowHeight == headingRowHeight + && other.headingTextStyle == headingTextStyle + && other.horizontalMargin == horizontalMargin + && other.columnSpacing == columnSpacing + && other.dividerThickness == dividerThickness; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('dataRowColor', dataRowColor, defaultValue: null)); + properties.add(DoubleProperty('dataRowHeight', dataRowHeight, defaultValue: null)); + properties.add(DiagnosticsProperty<TextStyle>('dataTextStyle', dataTextStyle, defaultValue: null)); + properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('headingRowColor', headingRowColor, defaultValue: null)); + properties.add(DoubleProperty('headingRowHeight', headingRowHeight, defaultValue: null)); + properties.add(DiagnosticsProperty<TextStyle>('headingTextStyle', headingTextStyle, defaultValue: null)); + properties.add(DoubleProperty('horizontalMargin', horizontalMargin, defaultValue: null)); + properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null)); + properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null)); + } + + static MaterialStateProperty<T> _lerpProperties<T>(MaterialStateProperty<T> a, MaterialStateProperty<T> b, double t, T Function(T, T, double) lerpFunction ) { + // Avoid creating a _LerpProperties object for a common case. + if (a == null && b == null) + return null; + return _LerpProperties<T>(a, b, t, lerpFunction); + } +} + +class _LerpProperties<T> implements MaterialStateProperty<T> { + const _LerpProperties(this.a, this.b, this.t, this.lerpFunction); + + final MaterialStateProperty<T> a; + final MaterialStateProperty<T> b; + final double t; + final T Function(T, T, double) lerpFunction; + + @override + T resolve(Set<MaterialState> states) { + final T resolvedA = a?.resolve(states); + final T resolvedB = b?.resolve(states); + return lerpFunction(resolvedA, resolvedB, t); + } +} + +/// Applies a data table theme to descendant [DataTable] widgets. +/// +/// Descendant widgets obtain the current theme's [DataTableTheme] object using +/// [DataTableTheme.of]. When a widget uses [DataTableTheme.of], it is +/// automatically rebuilt if the theme later changes. +/// +/// A data table theme can be specified as part of the overall Material +/// theme using [ThemeData.dataTableTheme]. +/// +/// See also: +/// +/// * [DataTableThemeData], which describes the actual configuration +/// of a data table theme. +class DataTableTheme extends InheritedWidget { + /// Constructs a data table theme that configures all descendant + /// [DataTable] widgets. + /// + /// The [data] must not be null. + const DataTableTheme({ + Key key, + @required this.data, + Widget child, + }) : assert(data != null), super(key: key, child: child); + + /// The properties used for all descendant [DataTable] widgets. + final DataTableThemeData data; + + /// Returns the configuration [data] from the closest + /// [DataTableTheme] ancestor. If there is no ancestor, it returns + /// [ThemeData.dataTableTheme]. Applications can assume that the + /// returned value will not be null. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// DataTableThemeData theme = DataTableTheme.of(context); + /// ``` + static DataTableThemeData of(BuildContext context) { + final DataTableTheme dataTableTheme = context.dependOnInheritedWidgetOfExactType<DataTableTheme>(); + return dataTableTheme?.data ?? Theme.of(context).dataTableTheme; + } + + @override + bool updateShouldNotify(DataTableTheme oldWidget) => data != oldWidget.data; +}
diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index a0e9937..1269a42 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart
@@ -476,6 +476,7 @@ horizontalMargin: widget.horizontalMargin, columnSpacing: widget.columnSpacing, showCheckboxColumn: widget.showCheckboxColumn, + showBottomBorder: true, rows: _getRows(_firstRowIndex, widget.rowsPerPage), ), ),
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 389d21f..566193e 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -22,6 +22,7 @@ import 'chip_theme.dart'; import 'color_scheme.dart'; import 'colors.dart'; +import 'data_table_theme.dart'; import 'dialog_theme.dart'; import 'divider_theme.dart'; import 'elevated_button_theme.dart'; @@ -283,6 +284,7 @@ ElevatedButtonThemeData elevatedButtonTheme, OutlinedButtonThemeData outlinedButtonTheme, TextSelectionThemeData textSelectionTheme, + DataTableThemeData dataTableTheme, bool fixTextFieldOutlineLabel, bool useTextSelectionTheme, }) { @@ -400,6 +402,7 @@ elevatedButtonTheme ??= const ElevatedButtonThemeData(); outlinedButtonTheme ??= const OutlinedButtonThemeData(); textSelectionTheme ??= const TextSelectionThemeData(); + dataTableTheme ??= const DataTableThemeData(); fixTextFieldOutlineLabel ??= false; useTextSelectionTheme ??= false; @@ -475,6 +478,7 @@ elevatedButtonTheme: elevatedButtonTheme, outlinedButtonTheme: outlinedButtonTheme, textSelectionTheme: textSelectionTheme, + dataTableTheme: dataTableTheme, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel, useTextSelectionTheme: useTextSelectionTheme, ); @@ -561,6 +565,7 @@ @required this.elevatedButtonTheme, @required this.outlinedButtonTheme, @required this.textSelectionTheme, + @required this.dataTableTheme, @required this.fixTextFieldOutlineLabel, @required this.useTextSelectionTheme, }) : assert(visualDensity != null), @@ -629,8 +634,8 @@ assert(textButtonTheme != null), assert(elevatedButtonTheme != null), assert(outlinedButtonTheme != null), - assert(fixTextFieldOutlineLabel != null), assert(textSelectionTheme != null), + assert(dataTableTheme != null), assert(fixTextFieldOutlineLabel != null), assert(useTextSelectionTheme != null); @@ -1107,6 +1112,10 @@ /// A theme for customizing the appearance and layout of [TextField] widgets. final TextSelectionThemeData textSelectionTheme; + /// A theme for customizing the appearance and layout of [DataTable] + /// widgets. + final DataTableThemeData dataTableTheme; + /// A temporary flag to allow apps to opt-in to a /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y /// coordinate of the floating label in a [TextField] [OutlineInputBorder]. @@ -1206,6 +1215,7 @@ ElevatedButtonThemeData elevatedButtonTheme, OutlinedButtonThemeData outlinedButtonTheme, TextSelectionThemeData textSelectionTheme, + DataTableThemeData dataTableTheme, bool fixTextFieldOutlineLabel, bool useTextSelectionTheme, }) { @@ -1281,6 +1291,7 @@ elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme, outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme, textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme, + dataTableTheme: dataTableTheme ?? this.dataTableTheme, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel, useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme, ); @@ -1434,6 +1445,7 @@ elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t), outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t), textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, t), + dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t), fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel, useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme, ); @@ -1515,6 +1527,7 @@ && other.elevatedButtonTheme == elevatedButtonTheme && other.outlinedButtonTheme == outlinedButtonTheme && other.textSelectionTheme == textSelectionTheme + && other.dataTableTheme == dataTableTheme && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel && other.useTextSelectionTheme == useTextSelectionTheme; } @@ -1595,6 +1608,7 @@ elevatedButtonTheme, outlinedButtonTheme, textSelectionTheme, + dataTableTheme, fixTextFieldOutlineLabel, useTextSelectionTheme, ]; @@ -1673,6 +1687,7 @@ properties.add(DiagnosticsProperty<TextButtonThemeData>('textButtonTheme', textButtonTheme, defaultValue: defaultData.textButtonTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<OutlinedButtonThemeData>('outlinedButtonTheme', outlinedButtonTheme, defaultValue: defaultData.outlinedButtonTheme, level: DiagnosticLevel.debug)); + properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug)); } }
diff --git a/packages/flutter/test/material/data_table_test.dart b/packages/flutter/test/material/data_table_test.dart index eee3175..e7edfeb 100644 --- a/packages/flutter/test/material/data_table_test.dart +++ b/packages/flutter/test/material/data_table_test.dart
@@ -177,6 +177,10 @@ MaterialApp( home: Material( child: DataTable( + headingTextStyle: const TextStyle( + fontSize: 14.0, + letterSpacing: 0.0, // Will overflow if letter spacing is larger than 0.0. + ), columns: <DataColumn>[ DataColumn( label: Text('X' * 2000), @@ -195,6 +199,7 @@ ), ), ); + expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, greaterThan(800.0)); expect(tester.renderObject<RenderBox>(find.byType(Row).first).size.width, greaterThan(800.0)); expect(tester.takeException(), isNull); // column overflows table, but text doesn't overflow cell @@ -692,7 +697,7 @@ ); // custom first column padding - padding = find.widgetWithText(Padding, 'Frozen yogurt'); + padding = find.widgetWithText(Padding, 'Frozen yogurt').first; cellContent = find.widgetWithText(Align, 'Frozen yogurt'); // DataTable wraps its DataCells in an Align widget expect( tester.getRect(cellContent).left - tester.getRect(padding).left, @@ -952,9 +957,9 @@ ); Table table = tester.widget(find.byType(Table)); - TableRow tableRow = table.children.first; + TableRow tableRow = table.children.last; BoxDecoration boxDecoration = tableRow.decoration as BoxDecoration; - expect(boxDecoration.border.bottom.width, 1.0); + expect(boxDecoration.border.top.width, 1.0); const double thickness = 4.2; await tester.pumpWidget( @@ -969,9 +974,58 @@ ), ); table = tester.widget(find.byType(Table)); - tableRow = table.children.first; + tableRow = table.children.last; boxDecoration = tableRow.decoration as BoxDecoration; - expect(boxDecoration.border.bottom.width, thickness); + expect(boxDecoration.border.top.width, thickness); + }); + + testWidgets('DataTable set show bottom border', (WidgetTester tester) async { + const List<DataColumn> columns = <DataColumn>[ + DataColumn(label: Text('column1')), + DataColumn(label: Text('column2')), + ]; + + const List<DataCell> cells = <DataCell>[ + DataCell(Text('cell1')), + DataCell(Text('cell2')), + ]; + + const List<DataRow> rows = <DataRow>[ + DataRow(cells: cells), + DataRow(cells: cells), + ]; + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: DataTable( + showBottomBorder: true, + columns: columns, + rows: rows, + ), + ), + ), + ); + + Table table = tester.widget(find.byType(Table)); + TableRow tableRow = table.children.last; + BoxDecoration boxDecoration = tableRow.decoration as BoxDecoration; + expect(boxDecoration.border.bottom.width, 1.0); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: DataTable( + columns: columns, + rows: rows, + ), + ), + ), + ); + table = tester.widget(find.byType(Table)); + tableRow = table.children.last; + boxDecoration = tableRow.decoration as BoxDecoration; + expect(boxDecoration.border.bottom.width, 0.0); }); testWidgets('DataTable column heading cell - with and without sorting', (WidgetTester tester) async { @@ -1225,7 +1279,30 @@ final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Content1'))); await tester.pump(const Duration(milliseconds: 200)); // splash is well underway final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; - expect(box, paints..circle(x: 64.0, y: 24.0, color: pressedColor)); + expect(box, paints..circle(x: 68.0, y: 24.0, color: pressedColor)); await gesture.up(); }); + + testWidgets('DataTable can render inside an AlertDialog', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Material( + child: AlertDialog( + content: DataTable( + columns: const <DataColumn>[ + DataColumn(label: Text('Col1')), + ], + rows: const <DataRow>[ + DataRow(cells: <DataCell>[DataCell(Text('1'))]), + ], + ), + scrollable: true, + ), + ), + ), + ); + + expect(tester.takeException(), isNull); + }); + }
diff --git a/packages/flutter/test/material/data_table_theme_test.dart b/packages/flutter/test/material/data_table_theme_test.dart new file mode 100644 index 0000000..192c789 --- /dev/null +++ b/packages/flutter/test/material/data_table_theme_test.dart
@@ -0,0 +1,252 @@ +// 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. + +// @dart = 2.8 + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('DataTableThemeData copyWith, ==, hashCode basics', () { + expect(const DataTableThemeData(), const DataTableThemeData().copyWith()); + expect(const DataTableThemeData().hashCode, const DataTableThemeData().copyWith().hashCode); + }); + + test('DataTableThemeData defaults', () { + const DataTableThemeData themeData = DataTableThemeData(); + expect(themeData.dataRowColor, null); + expect(themeData.dataRowHeight, null); + expect(themeData.dataTextStyle, null); + expect(themeData.headingRowColor, null); + expect(themeData.headingRowHeight, null); + expect(themeData.headingTextStyle, null); + expect(themeData.horizontalMargin, null); + expect(themeData.columnSpacing, null); + expect(themeData.dividerThickness, null); + + const DataTableTheme theme = DataTableTheme(data: DataTableThemeData()); + expect(theme.data.dataRowColor, null); + expect(theme.data.dataRowHeight, null); + expect(theme.data.dataTextStyle, null); + expect(theme.data.headingRowColor, null); + expect(theme.data.headingRowHeight, null); + expect(theme.data.headingTextStyle, null); + expect(theme.data.horizontalMargin, null); + expect(theme.data.columnSpacing, null); + expect(theme.data.dividerThickness, null); + }); + + testWidgets('Default DataTableThemeData debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + const DataTableThemeData().debugFillProperties(builder); + + final List<String> description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(); + + expect(description, <String>[]); + }); + + testWidgets('DataTableThemeData implements debugFillProperties', (WidgetTester tester) async { + final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + DataTableThemeData( + dataRowColor: MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff1), + ), + dataRowHeight: 51.0, + dataTextStyle: const TextStyle(fontSize: 12.0), + headingRowColor: MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff2), + ), + headingRowHeight: 52.0, + headingTextStyle: const TextStyle(fontSize: 14.0), + horizontalMargin: 3.0, + columnSpacing: 4.0, + dividerThickness: 5.0, + ).debugFillProperties(builder); + + final List<String> description = builder.properties + .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) + .map((DiagnosticsNode node) => node.toString()) + .toList(); + + expect(description[0], 'dataRowColor: Instance of \'_MaterialStatePropertyWith<Color>\''); + expect(description[1], 'dataRowHeight: 51.0'); + expect(description[2], 'dataTextStyle: TextStyle(inherit: true, size: 12.0)'); + expect(description[3], 'headingRowColor: Instance of \'_MaterialStatePropertyWith<Color>\''); + expect(description[4], 'headingRowHeight: 52.0'); + expect(description[5], 'headingTextStyle: TextStyle(inherit: true, size: 14.0)'); + expect(description[6], 'horizontalMargin: 3.0'); + expect(description[7], 'columnSpacing: 4.0'); + expect(description[8], 'dividerThickness: 5.0'); + }); + + testWidgets('DataTable is themeable', (WidgetTester tester) async { + final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff1), + ); + const double dataRowHeight = 51.0; + const TextStyle dataTextStyle = TextStyle(fontSize: 12.5); + final MaterialStateProperty<Color> headingRowColor = MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff2), + ); + const double headingRowHeight = 52.0; + const TextStyle headingTextStyle = TextStyle(fontSize: 14.5); + const double horizontalMargin = 3.0; + const double columnSpacing = 4.0; + const double dividerThickness = 5.0; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + dataTableTheme: DataTableThemeData( + dataRowColor: dataRowColor, + dataRowHeight: dataRowHeight, + dataTextStyle: dataTextStyle, + headingRowColor: headingRowColor, + headingRowHeight: headingRowHeight, + headingTextStyle: headingTextStyle, + horizontalMargin: horizontalMargin, + columnSpacing: columnSpacing, + dividerThickness: dividerThickness, + ), + ), + home: Scaffold( + body: DataTable( + sortColumnIndex: 0, + columns: <DataColumn>[ + DataColumn( + label: const Text('A'), + onSort: (int columnIndex, bool ascending) {}, + ), + const DataColumn(label: Text('B')), + ], + rows: const <DataRow>[ + DataRow(cells: <DataCell>[ + DataCell(Text('Data')), + DataCell(Text('Data 2')), + ]), + ], + ), + ), + ), + ); + + final TextStyle dataRowTextStyle = tester.renderObject<RenderParagraph>(find.text('Data')).text.style; + expect(dataRowTextStyle.fontSize, dataTextStyle.fontSize); + expect(_tableRowBoxDecoration(tester: tester, index: 1).color, dataRowColor.resolve(<MaterialState>{})); + expect(_tableRowBoxDecoration(tester: tester, index: 1).border.top.width, dividerThickness); + + final Finder dataRowContainer = find.ancestor(of: find.text('Data'), matching: find.byType(Container)); + expect(tester.getSize(dataRowContainer).height, dataRowHeight); + + final TextStyle headingRowTextStyle = tester.renderObject<RenderParagraph>(find.text('A')).text.style; + expect(headingRowTextStyle.fontSize, headingTextStyle.fontSize); + expect(_tableRowBoxDecoration(tester: tester, index: 0).color, headingRowColor.resolve(<MaterialState>{})); + + final Finder headingRowContainer = find.ancestor(of: find.text('A'), matching: find.byType(Container)); + expect(tester.getSize(headingRowContainer).height, headingRowHeight); + expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin); + expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing); + }); + + testWidgets('DataTable properties are taken over the theme values', (WidgetTester tester) async { + final MaterialStateProperty<Color> themeDataRowColor = MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff0), + ); + const double themeDataRowHeight = 50.0; + const TextStyle themeDataTextStyle = TextStyle(fontSize: 11.5); + final MaterialStateProperty<Color> themeHeadingRowColor = MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff1), + ); + const double themeHeadingRowHeight = 51.0; + const TextStyle themeHeadingTextStyle = TextStyle(fontSize: 13.5); + const double themeHorizontalMargin = 2.0; + const double themeColumnSpacing = 3.0; + const double themeDividerThickness = 4.0; + + final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff1), + ); + const double dataRowHeight = 51.0; + const TextStyle dataTextStyle = TextStyle(fontSize: 12.5); + final MaterialStateProperty<Color> headingRowColor = MaterialStateProperty.resolveWith<Color>( + (Set<MaterialState> states) => const Color(0xfffffff2), + ); + const double headingRowHeight = 52.0; + const TextStyle headingTextStyle = TextStyle(fontSize: 14.5); + const double horizontalMargin = 3.0; + const double columnSpacing = 4.0; + const double dividerThickness = 5.0; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + dataTableTheme: DataTableThemeData( + dataRowColor: themeDataRowColor, + dataRowHeight: themeDataRowHeight, + dataTextStyle: themeDataTextStyle, + headingRowColor: themeHeadingRowColor, + headingRowHeight: themeHeadingRowHeight, + headingTextStyle: themeHeadingTextStyle, + horizontalMargin: themeHorizontalMargin, + columnSpacing: themeColumnSpacing, + dividerThickness: themeDividerThickness, + ), + ), + home: Scaffold( + body: DataTable( + dataRowColor: dataRowColor, + dataRowHeight: dataRowHeight, + dataTextStyle: dataTextStyle, + headingRowColor: headingRowColor, + headingRowHeight: headingRowHeight, + headingTextStyle: headingTextStyle, + horizontalMargin: horizontalMargin, + columnSpacing: columnSpacing, + dividerThickness: dividerThickness, + sortColumnIndex: 0, + columns: <DataColumn>[ + DataColumn( + label: const Text('A'), + onSort: (int columnIndex, bool ascending) {}, + ), + const DataColumn(label: Text('B')), + ], + rows: const <DataRow>[ + DataRow(cells: <DataCell>[ + DataCell(Text('Data')), + DataCell(Text('Data 2')), + ]), + ], + ), + ), + ), + ); + + final TextStyle dataRowTextStyle = tester.renderObject<RenderParagraph>(find.text('Data')).text.style; + expect(dataRowTextStyle.fontSize, dataTextStyle.fontSize); + expect(_tableRowBoxDecoration(tester: tester, index: 1).color, dataRowColor.resolve(<MaterialState>{})); + expect(_tableRowBoxDecoration(tester: tester, index: 1).border.top.width, dividerThickness); + + final Finder dataRowContainer = find.ancestor(of: find.text('Data'), matching: find.byType(Container)); + expect(tester.getSize(dataRowContainer).height, dataRowHeight); + + final TextStyle headingRowTextStyle = tester.renderObject<RenderParagraph>(find.text('A')).text.style; + expect(headingRowTextStyle.fontSize, headingTextStyle.fontSize); + expect(_tableRowBoxDecoration(tester: tester, index: 0).color, headingRowColor.resolve(<MaterialState>{})); + + final Finder headingRowContainer = find.ancestor(of: find.text('A'), matching: find.byType(Container)); + expect(tester.getSize(headingRowContainer).height, headingRowHeight); + expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin); + expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing); + }); +} + +BoxDecoration _tableRowBoxDecoration({WidgetTester tester, int index}) { + final Table table = tester.widget(find.byType(Table)); + final TableRow tableRow = table.children[index]; + return tableRow.decoration as BoxDecoration; +}
diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index 3ebf2dc..2bc3bf8 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart
@@ -288,6 +288,7 @@ elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(primary: Colors.green)), outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)), textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black), + dataTableTheme: const DataTableThemeData(), fixTextFieldOutlineLabel: false, useTextSelectionTheme: false, ); @@ -376,6 +377,7 @@ elevatedButtonTheme: const ElevatedButtonThemeData(), outlinedButtonTheme: const OutlinedButtonThemeData(), textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white), + dataTableTheme: const DataTableThemeData(), fixTextFieldOutlineLabel: true, useTextSelectionTheme: true, );