Add decoration parameter to DataTable and DataTableTheme (#66640)

* Add decoration parameter to DataTable and DataTableTheme
This commit is contained in:
Per Classon 2020-10-09 14:11:17 +02:00 committed by GitHub
parent 0d8696f655
commit ee08c2278a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 159 additions and 70 deletions

View file

@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart';
import 'checkbox.dart';
import 'constants.dart';
import 'data_table_theme.dart';
import 'debug.dart';
import 'divider.dart';
import 'dropdown.dart';
@ -411,6 +412,7 @@ class DataTable extends StatelessWidget {
this.sortColumnIndex,
this.sortAscending = true,
this.onSelectAll,
this.decoration,
this.dataRowColor,
this.dataRowHeight,
this.dataTextStyle,
@ -473,6 +475,14 @@ class DataTable extends StatelessWidget {
/// row is selectable.
final ValueSetter<bool?>? onSelectAll;
/// {@template flutter.material.dataTable.decoration}
/// The background and border decoration for the table.
/// {@endtemplate}
///
/// If null, [DataTableThemeData.decoration] is used. By default there is no
/// decoration.
final Decoration? decoration;
/// {@template flutter.material.dataTable.dataRowColor}
/// The background color for the data rows.
///
@ -484,9 +494,10 @@ class DataTable extends StatelessWidget {
/// 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].
/// If null, [DataTableThemeData.dataRowColor] is used. 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
@ -511,15 +522,17 @@ class DataTable extends StatelessWidget {
/// 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.
/// If null, [DataTableThemeData.dataRowHeight] is used. 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].
/// If null, [DataTableThemeData.dataTextStyle] is used. By default, the text
/// style is [TextTheme.bodyText2].
final TextStyle? dataTextStyle;
/// {@template flutter.material.dataTable.headingRowColor}
@ -530,7 +543,11 @@ class DataTable extends StatelessWidget {
/// 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.
/// {@endtemplate}
///
/// If null, [DataTableThemeData.headingRowColor] is used.
///
/// {@template flutter.material.dataTable.headingRowColorCode}
/// ```dart
/// DataTable(
/// headingRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
@ -553,14 +570,16 @@ class DataTable extends StatelessWidget {
/// The height of the heading row.
/// {@endtemplate}
///
/// This value defaults to 56.0 to adhere to the Material Design specifications.
/// If null, [DataTableThemeData.headingRowHeight] is used. 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].
/// If null, [DataTableThemeData.headingTextStyle] is used. By default, the
/// text style is [TextTheme.subtitle2].
final TextStyle? headingTextStyle;
/// {@template flutter.material.dataTable.horizontalMargin}
@ -571,14 +590,16 @@ class DataTable extends StatelessWidget {
/// the content in the first data column.
/// {@endtemplate}
///
/// This value defaults to 24.0 to adhere to the Material Design specifications.
/// If null, [DataTableThemeData.horizontalMargin] is used. 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.
/// If null, [DataTableThemeData.columnSpacing] is used. This value defaults
/// to 56.0 to adhere to the Material Design specifications.
final double? columnSpacing;
/// {@template flutter.material.dataTable.showCheckboxColumn}
@ -603,13 +624,15 @@ class DataTable extends StatelessWidget {
///
/// Must be greater than or equal to zero.
/// {@endtemplate}
/// This value defaults to 1.0.
///
/// If null, [DataTableThemeData.dividerThickness] is used. 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.
/// around the table defined by [decoration].
final bool showBottomBorder;
// Set by the constructor to the index of the only Column that is
@ -975,9 +998,15 @@ class DataTable extends StatelessWidget {
displayColumnIndex += 1;
}
return Table(
columnWidths: tableColumns.asMap(),
children: tableRows,
return Container(
decoration: decoration ?? theme.dataTableTheme.decoration,
child: Material(
type: MaterialType.transparency,
child: Table(
columnWidths: tableColumns.asMap(),
children: tableRows,
),
),
);
}
}

View file

@ -35,6 +35,7 @@ import 'theme.dart';
class DataTableThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.dataTableTheme].
const DataTableThemeData({
this.decoration,
this.dataRowColor,
this.dataRowHeight,
this.dataTextStyle,
@ -46,6 +47,9 @@ class DataTableThemeData with Diagnosticable {
this.dividerThickness,
});
/// {@macro flutter.material.dataTable.decoration}
final Decoration? decoration;
/// {@macro flutter.material.dataTable.dataRowColor}
/// {@macro flutter.material.dataTable.dataRowColorCode}
final MaterialStateProperty<Color?>? dataRowColor;
@ -57,6 +61,7 @@ class DataTableThemeData with Diagnosticable {
final TextStyle? dataTextStyle;
/// {@macro flutter.material.dataTable.headingRowColor}
/// {@macro flutter.material.dataTable.headingRowColorCode}
final MaterialStateProperty<Color?>? headingRowColor;
/// {@macro flutter.material.dataTable.headingRowHeight}
@ -77,6 +82,7 @@ class DataTableThemeData with Diagnosticable {
/// 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,
@ -88,6 +94,7 @@ class DataTableThemeData with Diagnosticable {
double? dividerThickness,
}) {
return DataTableThemeData(
decoration: decoration ?? this.decoration,
dataRowColor: dataRowColor ?? this.dataRowColor,
dataRowHeight: dataRowHeight ?? this.dataRowHeight,
dataTextStyle: dataTextStyle ?? this.dataTextStyle,
@ -108,6 +115,7 @@ class DataTableThemeData with Diagnosticable {
static DataTableThemeData lerp(DataTableThemeData a, DataTableThemeData b, double t) {
assert(t != null);
return DataTableThemeData(
decoration: Decoration.lerp(a.decoration, b.decoration, t),
dataRowColor: _lerpProperties<Color?>(a.dataRowColor, b.dataRowColor, t, Color.lerp),
dataRowHeight: lerpDouble(a.dataRowHeight, b.dataRowHeight, t),
dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t),
@ -123,6 +131,7 @@ class DataTableThemeData with Diagnosticable {
@override
int get hashCode {
return hashValues(
decoration,
dataRowColor,
dataRowHeight,
dataTextStyle,
@ -142,6 +151,7 @@ class DataTableThemeData with Diagnosticable {
if (other.runtimeType != runtimeType)
return false;
return other is DataTableThemeData
&& other.decoration == decoration
&& other.dataRowColor == dataRowColor
&& other.dataRowHeight == dataRowHeight
&& other.dataTextStyle == dataTextStyle
@ -156,6 +166,7 @@ class DataTableThemeData with Diagnosticable {
@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));

View file

@ -471,41 +471,35 @@ void main() {
),
),
));
expect(tester.renderObject<RenderBox>(
find.widgetWithText(Container, 'Name')
).size.height, 56.0); // This is the header row height
expect(tester.renderObject<RenderBox>(
find.widgetWithText(Container, 'Frozen yogurt')
).size.height, 48.0); // This is the data row height
// The finder matches with the Container of the cell content, as well as the
// Container wrapping the whole table. The first one is used to test row
// heights.
Finder findFirstContainerFor(String text) => find.widgetWithText(Container, text).first;
expect(tester.getSize(findFirstContainerFor('Name')).height, 56.0);
expect(tester.getSize(findFirstContainerFor('Frozen yogurt')).height, 48.0);
// CUSTOM VALUES
await tester.pumpWidget(MaterialApp(
home: Material(child: buildCustomTable(headingRowHeight: 48.0)),
));
expect(tester.renderObject<RenderBox>(
find.widgetWithText(Container, 'Name')
).size.height, 48.0);
expect(tester.getSize(findFirstContainerFor('Name')).height, 48.0);
await tester.pumpWidget(MaterialApp(
home: Material(child: buildCustomTable(headingRowHeight: 64.0)),
));
expect(tester.renderObject<RenderBox>(
find.widgetWithText(Container, 'Name')
).size.height, 64.0);
expect(tester.getSize(findFirstContainerFor('Name')).height, 64.0);
await tester.pumpWidget(MaterialApp(
home: Material(child: buildCustomTable(dataRowHeight: 30.0)),
));
expect(tester.renderObject<RenderBox>(
find.widgetWithText(Container, 'Frozen yogurt')
).size.height, 30.0);
expect(tester.getSize(findFirstContainerFor('Frozen yogurt')).height, 30.0);
await tester.pumpWidget(MaterialApp(
home: Material(child: buildCustomTable(dataRowHeight: 56.0)),
));
expect(tester.renderObject<RenderBox>(
find.widgetWithText(Container, 'Frozen yogurt')
).size.height, 56.0);
expect(tester.getSize(findFirstContainerFor('Frozen yogurt')).height, 56.0);
});
testWidgets('DataTable custom horizontal padding - checkbox', (WidgetTester tester) async {
@ -1303,4 +1297,56 @@ void main() {
expect(tester.takeException(), isNull);
});
testWidgets('DataTable renders with border and background decoration', (WidgetTester tester) async {
const double width = 800;
const double height = 600;
const double borderHorizontal = 5.0;
const double borderVertical = 10.0;
const Color borderColor = Color(0xff2196f3);
const Color backgroundColor = Color(0xfff5f5f5);
await tester.pumpWidget(
MaterialApp(
home: DataTable(
decoration: const BoxDecoration(
color: backgroundColor,
border: Border.symmetric(
vertical: BorderSide(width: borderVertical, color: borderColor),
horizontal: BorderSide(width: borderHorizontal, color: borderColor),
),
),
columns: const <DataColumn>[
DataColumn(label: Text('Col1')),
],
rows: const <DataRow>[
DataRow(cells: <DataCell>[DataCell(Text('1'))]),
],
),
),
);
expect(
find.ancestor(of: find.byType(Table), matching: find.byType(Container)),
paints..rect(
rect: const Rect.fromLTRB(0.0, 0.0, width, height),
color: backgroundColor,
),
);
expect(
find.ancestor(of: find.byType(Table), matching: find.byType(Container)),
paints
..path(color: borderColor)
..path(color: borderColor)
..path(color: borderColor)
..path(color: borderColor),
);
expect(
tester.getTopLeft(find.byType(Table)),
const Offset(borderVertical, borderHorizontal),
);
expect(
tester.getBottomRight(find.byType(Table)),
const Offset(width - borderVertical, height - borderHorizontal),
);
});
}

View file

@ -14,6 +14,7 @@ void main() {
test('DataTableThemeData defaults', () {
const DataTableThemeData themeData = DataTableThemeData();
expect(themeData.decoration, null);
expect(themeData.dataRowColor, null);
expect(themeData.dataRowHeight, null);
expect(themeData.dataTextStyle, null);
@ -25,6 +26,7 @@ void main() {
expect(themeData.dividerThickness, null);
const DataTableTheme theme = DataTableTheme(data: DataTableThemeData(), child: SizedBox());
expect(theme.data.decoration, null);
expect(theme.data.dataRowColor, null);
expect(theme.data.dataRowHeight, null);
expect(theme.data.dataTextStyle, null);
@ -51,6 +53,7 @@ void main() {
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),
),
@ -71,26 +74,24 @@ void main() {
.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');
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 {
final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) => const Color(0xfffffff1),
);
const BoxDecoration decoration = BoxDecoration(color: Color(0xfffffff0));
final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.all<Color>(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),
);
final MaterialStateProperty<Color> headingRowColor = MaterialStateProperty.all<Color>(const Color(0xfffffff2));
const double headingRowHeight = 52.0;
const TextStyle headingTextStyle = TextStyle(fontSize: 14.5);
const double horizontalMargin = 3.0;
@ -101,6 +102,7 @@ void main() {
MaterialApp(
theme: ThemeData(
dataTableTheme: DataTableThemeData(
decoration: decoration,
dataRowColor: dataRowColor,
dataRowHeight: dataRowHeight,
dataTextStyle: dataTextStyle,
@ -133,47 +135,41 @@ void main() {
),
);
final Finder tableContainerFinder = find.ancestor(of: find.byType(Table), matching: find.byType(Container));
expect(tester.widgetList<Container>(tableContainerFinder).first.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);
expect(tester.getSize(_findFirstContainerFor('Data')).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.getSize(_findFirstContainerFor('A')).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 BoxDecoration themeDecoration = BoxDecoration(color: Color(0xfffffff1));
final MaterialStateProperty<Color> themeDataRowColor = MaterialStateProperty.all<Color>(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),
);
final MaterialStateProperty<Color> themeHeadingRowColor = MaterialStateProperty.all<Color>(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 BoxDecoration decoration = BoxDecoration(color: Color(0xfffffff0));
final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.all<Color>(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),
);
final MaterialStateProperty<Color> headingRowColor = MaterialStateProperty.all<Color>(const Color(0xfffffff2));
const double headingRowHeight = 52.0;
const TextStyle headingTextStyle = TextStyle(fontSize: 14.5);
const double horizontalMargin = 3.0;
@ -183,6 +179,7 @@ void main() {
MaterialApp(
theme: ThemeData(
dataTableTheme: DataTableThemeData(
decoration: themeDecoration,
dataRowColor: themeDataRowColor,
dataRowHeight: themeDataRowHeight,
dataTextStyle: themeDataTextStyle,
@ -196,6 +193,7 @@ void main() {
),
home: Scaffold(
body: DataTable(
decoration: decoration,
dataRowColor: dataRowColor,
dataRowHeight: dataRowHeight,
dataTextStyle: dataTextStyle,
@ -224,20 +222,20 @@ void main() {
),
);
final Finder tableContainerFinder = find.ancestor(of: find.byType(Table), matching: find.byType(Container));
expect(tester.widget<Container>(tableContainerFinder).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);
expect(tester.getSize(_findFirstContainerFor('Data')).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.getSize(_findFirstContainerFor('A')).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);
});
@ -248,3 +246,8 @@ BoxDecoration _tableRowBoxDecoration({required WidgetTester tester, required int
final TableRow tableRow = table.children[index];
return tableRow.decoration! as BoxDecoration;
}
// The finder matches with the Container of the cell content, as well as the
// Container wrapping the whole table. The first one is used to test row
// heights.
Finder _findFirstContainerFor(String text) => find.widgetWithText(Container, text).first;