Unverified Commit cb4b8677 authored by Per Classon's avatar Per Classon Committed by GitHub

Add DataTableTheme to allow for themable DataTables (#64316)

parent 61751835
...@@ -52,6 +52,7 @@ export 'src/material/constants.dart'; ...@@ -52,6 +52,7 @@ export 'src/material/constants.dart';
export 'src/material/curves.dart'; export 'src/material/curves.dart';
export 'src/material/data_table.dart'; export 'src/material/data_table.dart';
export 'src/material/data_table_source.dart'; export 'src/material/data_table_source.dart';
export 'src/material/data_table_theme.dart';
export 'src/material/debug.dart'; export 'src/material/debug.dart';
export 'src/material/dialog.dart'; export 'src/material/dialog.dart';
export 'src/material/dialog_theme.dart'; export 'src/material/dialog_theme.dart';
......
...@@ -11,12 +11,12 @@ import 'package:flutter/rendering.dart'; ...@@ -11,12 +11,12 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'checkbox.dart'; import 'checkbox.dart';
import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart'; import 'debug.dart';
import 'divider.dart'; import 'divider.dart';
import 'dropdown.dart'; import 'dropdown.dart';
import 'icons.dart'; import 'icons.dart';
import 'ink_decoration.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
import 'material_state.dart'; import 'material_state.dart';
...@@ -414,25 +414,27 @@ class DataTable extends StatelessWidget { ...@@ -414,25 +414,27 @@ class DataTable extends StatelessWidget {
this.sortColumnIndex, this.sortColumnIndex,
this.sortAscending = true, this.sortAscending = true,
this.onSelectAll, this.onSelectAll,
this.dataRowHeight = kMinInteractiveDimension, this.decoration,
this.headingRowHeight = 56.0, this.dataRowColor,
this.horizontalMargin = 24.0, this.dataRowHeight,
this.columnSpacing = 56.0, this.dataTextStyle,
this.headingRowColor,
this.headingRowHeight,
this.headingTextStyle,
this.horizontalMargin,
this.columnSpacing,
this.showCheckboxColumn = true, this.showCheckboxColumn = true,
this.dividerThickness = 1.0, this.showBottomBorder = false,
this.dividerThickness,
@required this.rows, @required this.rows,
}) : assert(columns != null), }) : assert(columns != null),
assert(columns.isNotEmpty), assert(columns.isNotEmpty),
assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
assert(sortAscending != null), assert(sortAscending != null),
assert(dataRowHeight != null),
assert(headingRowHeight != null),
assert(horizontalMargin != null),
assert(columnSpacing != null),
assert(showCheckboxColumn != null), assert(showCheckboxColumn != null),
assert(rows != null), assert(rows != null),
assert(!rows.any((DataRow row) => row.cells.length != columns.length)), assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
assert(dividerThickness != null && dividerThickness >= 0), assert(dividerThickness == null || dividerThickness >= 0),
_onlyTextColumn = _initOnlyTextColumn(columns), _onlyTextColumn = _initOnlyTextColumn(columns),
super(key: key); super(key: key);
...@@ -475,27 +477,117 @@ class DataTable extends StatelessWidget { ...@@ -475,27 +477,117 @@ class DataTable extends StatelessWidget {
/// row is selectable. /// row is selectable.
final ValueSetter<bool> onSelectAll; final ValueSetter<bool> onSelectAll;
/// {@template flutter.material.dataTable.decoration}
/// The background and border decoration for the table.
/// {@endtemplate}
///
/// By default there is no decoration.
final BoxDecoration decoration;
/// {@template flutter.material.dataTable.dataRowColor}
/// The background color for the data rows.
///
/// 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). /// The height of each row (excluding the row that contains column headings).
/// {@endtemplate}
/// ///
/// This value defaults to kMinInteractiveDimension to adhere to the Material /// This value defaults to [kMinInteractiveDimension] to adhere to the Material
/// Design specifications. /// Design specifications.
final double dataRowHeight; 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. /// The height of the heading row.
/// {@endtemplate}
/// ///
/// This value defaults to 56.0 to adhere to the Material Design specifications. /// This value defaults to 56.0 to adhere to the Material Design specifications.
final double headingRowHeight; 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 /// The horizontal margin between the edges of the table and the content
/// in the first and last cells of each row. /// in the first and last cells of each row.
/// ///
/// When a checkbox is displayed, it is also the margin between the checkbox /// When a checkbox is displayed, it is also the margin between the checkbox
/// the content in the first data column. /// the content in the first data column.
/// {@endtemplate}
/// ///
/// This value defaults to 24.0 to adhere to the Material Design specifications. /// This value defaults to 24.0 to adhere to the Material Design specifications.
final double horizontalMargin; final double horizontalMargin;
/// {@template flutter.material.dataTable.columnSpacing}
/// The horizontal margin between the contents of each data column. /// The horizontal margin between the contents of each data column.
/// {@endtemplate}
/// ///
/// This value defaults to 56.0 to adhere to the Material Design specifications. /// This value defaults to 56.0 to adhere to the Material Design specifications.
final double columnSpacing; final double columnSpacing;
...@@ -517,6 +609,20 @@ class DataTable extends StatelessWidget { ...@@ -517,6 +609,20 @@ class DataTable extends StatelessWidget {
/// Must be non-null, but may be empty. /// Must be non-null, but may be empty.
final List<DataRow> rows; 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 set with [DataTable.decoration].
final bool showBottomBorder;
// Set by the constructor to the index of the only Column that is // Set by the constructor to the index of the only Column that is
// non-numeric, if there is exactly one, otherwise null. // non-numeric, if there is exactly one, otherwise null.
final int _onlyTextColumn; final int _onlyTextColumn;
...@@ -551,29 +657,45 @@ class DataTable extends StatelessWidget { ...@@ -551,29 +657,45 @@ class DataTable extends StatelessWidget {
} }
} }
/// 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 _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 Duration _sortArrowAnimationDuration = Duration(milliseconds: 150);
static const Color _grey100Opacity = Color(0x0A000000); // Grey 100 as opacity instead of solid color 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. 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({ Widget _buildCheckbox({
BuildContext context,
Color activeColor, Color activeColor,
bool checked, bool checked,
VoidCallback onRowTap, VoidCallback onRowTap,
ValueChanged<bool> onCheckboxChanged, ValueChanged<bool> onCheckboxChanged,
MaterialStateProperty<Color> overlayColor, MaterialStateProperty<Color> overlayColor,
}) { }) {
final ThemeData themeData = Theme.of(context);
final double effectiveHorizontalMargin = horizontalMargin
?? themeData.dataTableTheme.horizontalMargin
?? _horizontalMargin;
Widget contents = Semantics( Widget contents = Semantics(
container: true, container: true,
child: Padding( child: Padding(
padding: EdgeInsetsDirectional.only(start: horizontalMargin, end: horizontalMargin / 2.0), padding: EdgeInsetsDirectional.only(
start: effectiveHorizontalMargin,
end: effectiveHorizontalMargin / 2.0,
),
child: Center( child: Center(
child: Checkbox( child: Checkbox(
activeColor: activeColor, activeColor: activeColor,
...@@ -605,39 +727,37 @@ class DataTable extends StatelessWidget { ...@@ -605,39 +727,37 @@ class DataTable extends StatelessWidget {
VoidCallback onSort, VoidCallback onSort,
bool sorted, bool sorted,
bool ascending, bool ascending,
MaterialStateProperty<Color> overlayColor,
}) { }) {
List<Widget> arrowWithPadding() { final ThemeData themeData = Theme.of(context);
return onSort == null ? const <Widget>[] : <Widget>[
_SortArrow(
visible: sorted,
up: sorted ? ascending : null,
duration: _sortArrowAnimationDuration,
),
const SizedBox(width: _sortArrowPadding),
];
}
label = Row( label = Row(
textDirection: numeric ? TextDirection.rtl : null, textDirection: numeric ? TextDirection.rtl : null,
children: <Widget>[ children: <Widget>[
label, 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( label = Container(
padding: padding, padding: padding,
height: headingRowHeight, height: effectiveHeadingRowHeight,
alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart, alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
child: AnimatedDefaultTextStyle( child: AnimatedDefaultTextStyle(
style: TextStyle( style: effectiveHeadingTextStyle,
// 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),
),
softWrap: false, softWrap: false,
duration: _sortArrowAnimationDuration, duration: _sortArrowAnimationDuration,
child: label, child: label,
...@@ -649,10 +769,12 @@ class DataTable extends StatelessWidget { ...@@ -649,10 +769,12 @@ class DataTable extends StatelessWidget {
child: label, child: label,
); );
} }
// TODO(dkwingsmt): Only wrap Inkwell if onSort != null. Blocked by // TODO(dkwingsmt): Only wrap Inkwell if onSort != null. Blocked by
// https://github.com/flutter/flutter/issues/51152 // https://github.com/flutter/flutter/issues/51152
label = InkWell( label = InkWell(
onTap: onSort, onTap: onSort,
overlayColor: overlayColor,
child: label, child: label,
); );
return label; return label;
...@@ -669,7 +791,7 @@ class DataTable extends StatelessWidget { ...@@ -669,7 +791,7 @@ class DataTable extends StatelessWidget {
VoidCallback onSelectChanged, VoidCallback onSelectChanged,
MaterialStateProperty<Color> overlayColor, MaterialStateProperty<Color> overlayColor,
}) { }) {
final bool isLightTheme = Theme.of(context).brightness == Brightness.light; final ThemeData themeData = Theme.of(context);
if (showEditIcon) { if (showEditIcon) {
const Widget icon = Icon(Icons.edit, size: 18.0); const Widget icon = Icon(Icons.edit, size: 18.0);
label = Expanded(child: label); label = Expanded(child: label);
...@@ -678,25 +800,22 @@ class DataTable extends StatelessWidget { ...@@ -678,25 +800,22 @@ class DataTable extends StatelessWidget {
children: <Widget>[ label, icon ], children: <Widget>[ label, icon ],
); );
} }
final TextStyle effectiveDataTextStyle = dataTextStyle
?? themeData.dataTableTheme.dataTextStyle
?? themeData.textTheme.bodyText2;
final double effectiveDataRowHeight = dataRowHeight
?? themeData.dataTableTheme.dataRowHeight
?? kMinInteractiveDimension;
label = Container( label = Container(
padding: padding, padding: padding,
height: dataRowHeight, height: effectiveDataRowHeight,
alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart, alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
child: DefaultTextStyle( child: DefaultTextStyle(
style: TextStyle( style: effectiveDataTextStyle.copyWith(
// TODO(hansmuller): This should use the information provided by color: placeholder ? effectiveDataTextStyle.color.withOpacity(0.6) : null,
// textTheme/DataTableTheme, https://github.com/flutter/flutter/issues/56079
fontSize: 13.0,
color: isLightTheme
? (placeholder ? Colors.black38 : Colors.black87)
: (placeholder ? Colors.white38 : Colors.white70),
),
child: IconTheme.merge(
data: IconThemeData(
color: isLightTheme ? Colors.black54 : Colors.white70,
),
child: DropdownButtonHideUnderline(child: label),
), ),
child: DropdownButtonHideUnderline(child: label),
), ),
); );
if (onTap != null) { if (onTap != null) {
...@@ -720,12 +839,16 @@ class DataTable extends StatelessWidget { ...@@ -720,12 +839,16 @@ class DataTable extends StatelessWidget {
assert(!_debugInteractive || debugCheckHasMaterial(context)); assert(!_debugInteractive || debugCheckHasMaterial(context));
final ThemeData theme = Theme.of(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( final MaterialStateProperty<Color> defaultRowColor = MaterialStateProperty.resolveWith(
(Set<MaterialState> states) { (Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) { 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 color has to be transparent so you can see the ink on
// the [Material]. // 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) ? return (Theme.of(context).brightness == Brightness.light) ?
_grey100Opacity : _grey300Opacity; _grey100Opacity : _grey300Opacity;
} }
...@@ -735,6 +858,12 @@ class DataTable extends StatelessWidget { ...@@ -735,6 +858,12 @@ class DataTable extends StatelessWidget {
final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null); final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null);
final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable; final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable;
final bool allChecked = displayCheckboxColumn && !rows.any((DataRow row) => row.onSelectChanged != null && !row.selected); 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<TableColumnWidth> tableColumns = List<TableColumnWidth>(columns.length + (displayCheckboxColumn ? 1 : 0));
final List<TableRow> tableRows = List<TableRow>.generate( final List<TableRow> tableRows = List<TableRow>.generate(
...@@ -748,13 +877,22 @@ class DataTable extends StatelessWidget { ...@@ -748,13 +877,22 @@ class DataTable extends StatelessWidget {
if (isDisabled) if (isDisabled)
MaterialState.disabled, 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( return TableRow(
key: index == 0 ? _headingRowKey : rows[index - 1].key, key: index == 0 ? _headingRowKey : rows[index - 1].key,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: border,
bottom: Divider.createBorderSide(context, width: dividerThickness),
),
color: rowColor ?? defaultRowColor.resolve(states), color: rowColor ?? defaultRowColor.resolve(states),
), ),
children: List<Widget>(tableColumns.length), children: List<Widget>(tableColumns.length),
...@@ -766,8 +904,9 @@ class DataTable extends StatelessWidget { ...@@ -766,8 +904,9 @@ class DataTable extends StatelessWidget {
int displayColumnIndex = 0; int displayColumnIndex = 0;
if (displayCheckboxColumn) { 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( tableRows[0].children[0] = _buildCheckbox(
context: context,
activeColor: theme.accentColor, activeColor: theme.accentColor,
checked: allChecked, checked: allChecked,
onCheckboxChanged: _handleSelectAll, onCheckboxChanged: _handleSelectAll,
...@@ -775,11 +914,12 @@ class DataTable extends StatelessWidget { ...@@ -775,11 +914,12 @@ class DataTable extends StatelessWidget {
rowIndex = 1; rowIndex = 1;
for (final DataRow row in rows) { for (final DataRow row in rows) {
tableRows[rowIndex].children[0] = _buildCheckbox( tableRows[rowIndex].children[0] = _buildCheckbox(
context: context,
activeColor: theme.accentColor, activeColor: theme.accentColor,
checked: row.selected, checked: row.selected,
onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null , onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null ,
onCheckboxChanged: row.onSelectChanged, onCheckboxChanged: row.onSelectChanged,
overlayColor: row.color, overlayColor: row.color ?? effectiveDataRowColor,
); );
rowIndex += 1; rowIndex += 1;
} }
...@@ -791,18 +931,18 @@ class DataTable extends StatelessWidget { ...@@ -791,18 +931,18 @@ class DataTable extends StatelessWidget {
double paddingStart; double paddingStart;
if (dataColumnIndex == 0 && displayCheckboxColumn) { if (dataColumnIndex == 0 && displayCheckboxColumn) {
paddingStart = horizontalMargin / 2.0; paddingStart = effectiveHorizontalMargin / 2.0;
} else if (dataColumnIndex == 0 && !displayCheckboxColumn) { } else if (dataColumnIndex == 0 && !displayCheckboxColumn) {
paddingStart = horizontalMargin; paddingStart = effectiveHorizontalMargin;
} else { } else {
paddingStart = columnSpacing / 2.0; paddingStart = effectiveColumnSpacing / 2.0;
} }
double paddingEnd; double paddingEnd;
if (dataColumnIndex == columns.length - 1) { if (dataColumnIndex == columns.length - 1) {
paddingEnd = horizontalMargin; paddingEnd = effectiveHorizontalMargin;
} else { } else {
paddingEnd = columnSpacing / 2.0; paddingEnd = effectiveColumnSpacing / 2.0;
} }
final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only( final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
...@@ -823,6 +963,7 @@ class DataTable extends StatelessWidget { ...@@ -823,6 +963,7 @@ class DataTable extends StatelessWidget {
onSort: column.onSort != null ? () => column.onSort(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) : null, onSort: column.onSort != null ? () => column.onSort(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) : null,
sorted: dataColumnIndex == sortColumnIndex, sorted: dataColumnIndex == sortColumnIndex,
ascending: sortAscending, ascending: sortAscending,
overlayColor: effectiveHeadingRowColor,
); );
rowIndex = 1; rowIndex = 1;
for (final DataRow row in rows) { for (final DataRow row in rows) {
...@@ -836,16 +977,19 @@ class DataTable extends StatelessWidget { ...@@ -836,16 +977,19 @@ class DataTable extends StatelessWidget {
showEditIcon: cell.showEditIcon, showEditIcon: cell.showEditIcon,
onTap: cell.onTap, onTap: cell.onTap,
onSelectChanged: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null, onSelectChanged: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null,
overlayColor: row.color, overlayColor: row.color ?? effectiveDataRowColor,
); );
rowIndex += 1; rowIndex += 1;
} }
displayColumnIndex += 1; displayColumnIndex += 1;
} }
return Table( return Ink(
columnWidths: tableColumns.asMap(), decoration: decoration ?? theme.dataTableTheme.decoration,
children: tableRows, child: Table(
columnWidths: tableColumns.asMap(),
children: tableRows,
),
); );
} }
} }
...@@ -1035,10 +1179,9 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { ...@@ -1035,10 +1179,9 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
transform: Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value) transform: Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value)
..setTranslationRaw(0.0, _arrowIconBaselineOffset, 0.0), ..setTranslationRaw(0.0, _arrowIconBaselineOffset, 0.0),
alignment: Alignment.center, alignment: Alignment.center,
child: Icon( child: const Icon(
Icons.arrow_upward, Icons.arrow_upward,
size: _arrowIconSize, size: _arrowIconSize,
color: (Theme.of(context).brightness == Brightness.light) ? Colors.black87 : Colors.white70,
), ),
), ),
); );
......
// 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.decoration,
this.dataRowColor,
this.dataRowHeight,
this.dataTextStyle,
this.headingRowColor,
this.headingRowHeight,
this.headingTextStyle,
this.horizontalMargin,
this.columnSpacing,
this.dividerThickness,
});
/// {@macro flutter.material.dataTable.decoration}
final Decoration decoration;
/// {@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({
Decoration decoration,
MaterialStateProperty<Color> dataRowColor,
double dataRowHeight,
TextStyle dataTextStyle,
MaterialStateProperty<Color> headingRowColor,
double headingRowHeight,
TextStyle headingTextStyle,
double horizontalMargin,
double columnSpacing,
double dividerThickness,
}) {
return DataTableThemeData(
decoration: decoration ?? this.decoration,
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(
decoration: Decoration.lerp(a.decoration, b.decoration, t),
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(
decoration,
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.decoration == decoration
&& 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<Decoration>('decoration', decoration, defaultValue: null));
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;
}
...@@ -476,6 +476,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> { ...@@ -476,6 +476,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
horizontalMargin: widget.horizontalMargin, horizontalMargin: widget.horizontalMargin,
columnSpacing: widget.columnSpacing, columnSpacing: widget.columnSpacing,
showCheckboxColumn: widget.showCheckboxColumn, showCheckboxColumn: widget.showCheckboxColumn,
showBottomBorder: true,
rows: _getRows(_firstRowIndex, widget.rowsPerPage), rows: _getRows(_firstRowIndex, widget.rowsPerPage),
), ),
), ),
......
...@@ -22,6 +22,7 @@ import 'card_theme.dart'; ...@@ -22,6 +22,7 @@ import 'card_theme.dart';
import 'chip_theme.dart'; import 'chip_theme.dart';
import 'color_scheme.dart'; import 'color_scheme.dart';
import 'colors.dart'; import 'colors.dart';
import 'data_table_theme.dart';
import 'dialog_theme.dart'; import 'dialog_theme.dart';
import 'divider_theme.dart'; import 'divider_theme.dart';
import 'elevated_button_theme.dart'; import 'elevated_button_theme.dart';
...@@ -283,6 +284,7 @@ class ThemeData with Diagnosticable { ...@@ -283,6 +284,7 @@ class ThemeData with Diagnosticable {
ElevatedButtonThemeData elevatedButtonTheme, ElevatedButtonThemeData elevatedButtonTheme,
OutlinedButtonThemeData outlinedButtonTheme, OutlinedButtonThemeData outlinedButtonTheme,
TextSelectionThemeData textSelectionTheme, TextSelectionThemeData textSelectionTheme,
DataTableThemeData dataTableTheme,
bool fixTextFieldOutlineLabel, bool fixTextFieldOutlineLabel,
bool useTextSelectionTheme, bool useTextSelectionTheme,
}) { }) {
...@@ -400,6 +402,7 @@ class ThemeData with Diagnosticable { ...@@ -400,6 +402,7 @@ class ThemeData with Diagnosticable {
elevatedButtonTheme ??= const ElevatedButtonThemeData(); elevatedButtonTheme ??= const ElevatedButtonThemeData();
outlinedButtonTheme ??= const OutlinedButtonThemeData(); outlinedButtonTheme ??= const OutlinedButtonThemeData();
textSelectionTheme ??= const TextSelectionThemeData(); textSelectionTheme ??= const TextSelectionThemeData();
dataTableTheme ??= const DataTableThemeData();
fixTextFieldOutlineLabel ??= false; fixTextFieldOutlineLabel ??= false;
useTextSelectionTheme ??= false; useTextSelectionTheme ??= false;
...@@ -475,6 +478,7 @@ class ThemeData with Diagnosticable { ...@@ -475,6 +478,7 @@ class ThemeData with Diagnosticable {
elevatedButtonTheme: elevatedButtonTheme, elevatedButtonTheme: elevatedButtonTheme,
outlinedButtonTheme: outlinedButtonTheme, outlinedButtonTheme: outlinedButtonTheme,
textSelectionTheme: textSelectionTheme, textSelectionTheme: textSelectionTheme,
dataTableTheme: dataTableTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
useTextSelectionTheme: useTextSelectionTheme, useTextSelectionTheme: useTextSelectionTheme,
); );
...@@ -561,6 +565,7 @@ class ThemeData with Diagnosticable { ...@@ -561,6 +565,7 @@ class ThemeData with Diagnosticable {
@required this.elevatedButtonTheme, @required this.elevatedButtonTheme,
@required this.outlinedButtonTheme, @required this.outlinedButtonTheme,
@required this.textSelectionTheme, @required this.textSelectionTheme,
@required this.dataTableTheme,
@required this.fixTextFieldOutlineLabel, @required this.fixTextFieldOutlineLabel,
@required this.useTextSelectionTheme, @required this.useTextSelectionTheme,
}) : assert(visualDensity != null), }) : assert(visualDensity != null),
...@@ -629,8 +634,8 @@ class ThemeData with Diagnosticable { ...@@ -629,8 +634,8 @@ class ThemeData with Diagnosticable {
assert(textButtonTheme != null), assert(textButtonTheme != null),
assert(elevatedButtonTheme != null), assert(elevatedButtonTheme != null),
assert(outlinedButtonTheme != null), assert(outlinedButtonTheme != null),
assert(fixTextFieldOutlineLabel != null),
assert(textSelectionTheme != null), assert(textSelectionTheme != null),
assert(dataTableTheme != null),
assert(fixTextFieldOutlineLabel != null), assert(fixTextFieldOutlineLabel != null),
assert(useTextSelectionTheme != null); assert(useTextSelectionTheme != null);
...@@ -1107,6 +1112,10 @@ class ThemeData with Diagnosticable { ...@@ -1107,6 +1112,10 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the appearance and layout of [TextField] widgets. /// A theme for customizing the appearance and layout of [TextField] widgets.
final TextSelectionThemeData textSelectionTheme; 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 /// A temporary flag to allow apps to opt-in to a
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder]. /// coordinate of the floating label in a [TextField] [OutlineInputBorder].
...@@ -1206,6 +1215,7 @@ class ThemeData with Diagnosticable { ...@@ -1206,6 +1215,7 @@ class ThemeData with Diagnosticable {
ElevatedButtonThemeData elevatedButtonTheme, ElevatedButtonThemeData elevatedButtonTheme,
OutlinedButtonThemeData outlinedButtonTheme, OutlinedButtonThemeData outlinedButtonTheme,
TextSelectionThemeData textSelectionTheme, TextSelectionThemeData textSelectionTheme,
DataTableThemeData dataTableTheme,
bool fixTextFieldOutlineLabel, bool fixTextFieldOutlineLabel,
bool useTextSelectionTheme, bool useTextSelectionTheme,
}) { }) {
...@@ -1281,6 +1291,7 @@ class ThemeData with Diagnosticable { ...@@ -1281,6 +1291,7 @@ class ThemeData with Diagnosticable {
elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme, elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme,
outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme, outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme,
textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme, textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme,
dataTableTheme: dataTableTheme ?? this.dataTableTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme, useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme,
); );
...@@ -1434,6 +1445,7 @@ class ThemeData with Diagnosticable { ...@@ -1434,6 +1445,7 @@ class ThemeData with Diagnosticable {
elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t), elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t),
outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t), outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t),
textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, 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, fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme, useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme,
); );
...@@ -1515,6 +1527,7 @@ class ThemeData with Diagnosticable { ...@@ -1515,6 +1527,7 @@ class ThemeData with Diagnosticable {
&& other.elevatedButtonTheme == elevatedButtonTheme && other.elevatedButtonTheme == elevatedButtonTheme
&& other.outlinedButtonTheme == outlinedButtonTheme && other.outlinedButtonTheme == outlinedButtonTheme
&& other.textSelectionTheme == textSelectionTheme && other.textSelectionTheme == textSelectionTheme
&& other.dataTableTheme == dataTableTheme
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel
&& other.useTextSelectionTheme == useTextSelectionTheme; && other.useTextSelectionTheme == useTextSelectionTheme;
} }
...@@ -1595,6 +1608,7 @@ class ThemeData with Diagnosticable { ...@@ -1595,6 +1608,7 @@ class ThemeData with Diagnosticable {
elevatedButtonTheme, elevatedButtonTheme,
outlinedButtonTheme, outlinedButtonTheme,
textSelectionTheme, textSelectionTheme,
dataTableTheme,
fixTextFieldOutlineLabel, fixTextFieldOutlineLabel,
useTextSelectionTheme, useTextSelectionTheme,
]; ];
...@@ -1673,6 +1687,7 @@ class ThemeData with Diagnosticable { ...@@ -1673,6 +1687,7 @@ class ThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<TextButtonThemeData>('textButtonTheme', textButtonTheme, defaultValue: defaultData.textButtonTheme, level: DiagnosticLevel.debug)); 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<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<OutlinedButtonThemeData>('outlinedButtonTheme', outlinedButtonTheme, defaultValue: defaultData.outlinedButtonTheme, 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));
} }
} }
......
...@@ -177,6 +177,10 @@ void main() { ...@@ -177,6 +177,10 @@ void main() {
MaterialApp( MaterialApp(
home: Material( home: Material(
child: DataTable( child: DataTable(
headingTextStyle: const TextStyle(
fontSize: 14.0,
letterSpacing: 0.0, // Will overflow if letter spacing is larger than 0.0.
),
columns: <DataColumn>[ columns: <DataColumn>[
DataColumn( DataColumn(
label: Text('X' * 2000), label: Text('X' * 2000),
...@@ -195,6 +199,7 @@ void main() { ...@@ -195,6 +199,7 @@ void main() {
), ),
), ),
); );
expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, greaterThan(800.0)); 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.renderObject<RenderBox>(find.byType(Row).first).size.width, greaterThan(800.0));
expect(tester.takeException(), isNull); // column overflows table, but text doesn't overflow cell expect(tester.takeException(), isNull); // column overflows table, but text doesn't overflow cell
...@@ -692,7 +697,7 @@ void main() { ...@@ -692,7 +697,7 @@ void main() {
); );
// custom first column padding // 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 cellContent = find.widgetWithText(Align, 'Frozen yogurt'); // DataTable wraps its DataCells in an Align widget
expect( expect(
tester.getRect(cellContent).left - tester.getRect(padding).left, tester.getRect(cellContent).left - tester.getRect(padding).left,
...@@ -952,9 +957,9 @@ void main() { ...@@ -952,9 +957,9 @@ void main() {
); );
Table table = tester.widget(find.byType(Table)); Table table = tester.widget(find.byType(Table));
TableRow tableRow = table.children.first; TableRow tableRow = table.children.last;
BoxDecoration boxDecoration = tableRow.decoration as BoxDecoration; 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; const double thickness = 4.2;
await tester.pumpWidget( await tester.pumpWidget(
...@@ -969,9 +974,58 @@ void main() { ...@@ -969,9 +974,58 @@ void main() {
), ),
); );
table = tester.widget(find.byType(Table)); table = tester.widget(find.byType(Table));
tableRow = table.children.first; tableRow = table.children.last;
boxDecoration = tableRow.decoration as BoxDecoration;
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; boxDecoration = tableRow.decoration as BoxDecoration;
expect(boxDecoration.border.bottom.width, thickness); expect(boxDecoration.border.bottom.width, 0.0);
}); });
testWidgets('DataTable column heading cell - with and without sorting', (WidgetTester tester) async { testWidgets('DataTable column heading cell - with and without sorting', (WidgetTester tester) async {
...@@ -1225,7 +1279,7 @@ void main() { ...@@ -1225,7 +1279,7 @@ void main() {
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Content1'))); final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Content1')));
await tester.pump(const Duration(milliseconds: 200)); // splash is well underway await tester.pump(const Duration(milliseconds: 200)); // splash is well underway
final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox; 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(); await gesture.up();
}); });
} }
// 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.decoration, null);
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.decoration, null);
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(
decoration: const BoxDecoration(color: Color(0xfffffff0)),
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], 'decoration: BoxDecoration(color: Color(0xfffffff0))');
expect(description[1], 'dataRowColor: Instance of \'_MaterialStatePropertyWith<Color>\'');
expect(description[2], 'dataRowHeight: 51.0');
expect(description[3], 'dataTextStyle: TextStyle(inherit: true, size: 12.0)');
expect(description[4], 'headingRowColor: Instance of \'_MaterialStatePropertyWith<Color>\'');
expect(description[5], 'headingRowHeight: 52.0');
expect(description[6], 'headingTextStyle: TextStyle(inherit: true, size: 14.0)');
expect(description[7], 'horizontalMargin: 3.0');
expect(description[8], 'columnSpacing: 4.0');
expect(description[9], 'dividerThickness: 5.0');
});
testWidgets('DataTable is themeable', (WidgetTester tester) async {
const BoxDecoration decoration = BoxDecoration(color: Color(0xfffffff0));
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(
decoration: decoration,
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 Finder dataTableInkFinder = find.descendant(of: find.byType(DataTable), matching: find.byType(Ink));
expect(tester.widgetList<Ink>(dataTableInkFinder).last.decoration, decoration);
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 {
const BoxDecoration themeDecoration = BoxDecoration(color: Color(0xfffffff1));
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;
const BoxDecoration decoration = BoxDecoration(color: Color(0xfffffff0));
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(
decoration: themeDecoration,
dataRowColor: themeDataRowColor,
dataRowHeight: themeDataRowHeight,
dataTextStyle: themeDataTextStyle,
headingRowColor: themeHeadingRowColor,
headingRowHeight: themeHeadingRowHeight,
headingTextStyle: themeHeadingTextStyle,
horizontalMargin: themeHorizontalMargin,
columnSpacing: themeColumnSpacing,
dividerThickness: themeDividerThickness,
),
),
home: Scaffold(
body: DataTable(
decoration: decoration,
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 Finder dataTableInkFinder = find.descendant(of: find.byType(DataTable), matching: find.byType(Ink));
expect(tester.widgetList<Ink>(dataTableInkFinder).last.decoration, decoration);
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;
}
...@@ -288,6 +288,7 @@ void main() { ...@@ -288,6 +288,7 @@ void main() {
elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(primary: Colors.green)), elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(primary: Colors.green)),
outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)), outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)),
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black), textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black),
dataTableTheme: const DataTableThemeData(),
fixTextFieldOutlineLabel: false, fixTextFieldOutlineLabel: false,
useTextSelectionTheme: false, useTextSelectionTheme: false,
); );
...@@ -376,6 +377,7 @@ void main() { ...@@ -376,6 +377,7 @@ void main() {
elevatedButtonTheme: const ElevatedButtonThemeData(), elevatedButtonTheme: const ElevatedButtonThemeData(),
outlinedButtonTheme: const OutlinedButtonThemeData(), outlinedButtonTheme: const OutlinedButtonThemeData(),
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white), textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white),
dataTableTheme: const DataTableThemeData(),
fixTextFieldOutlineLabel: true, fixTextFieldOutlineLabel: true,
useTextSelectionTheme: true, useTextSelectionTheme: true,
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment