diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index 25394b233a..c16ebe5ba7 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -262,6 +262,8 @@ class DataTable extends StatelessWidget { this.sortColumnIndex, this.sortAscending = true, this.onSelectAll, + this.dataRowHeight = 48.0, + this.headingRowHeight = 56.0, this.horizontalMargin = 24.0, this.columnSpacing = 56.0, @required this.rows, @@ -269,6 +271,8 @@ class DataTable extends StatelessWidget { 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(rows != null), @@ -315,6 +319,16 @@ class DataTable extends StatelessWidget { /// row is selectable. final ValueSetter onSelectAll; + /// The height of each row (excluding the row that contains column headings). + /// + /// This value defaults to 48.0 to adhere to the Material Design specifications. + final double dataRowHeight; + + /// The height of the heading row. + /// + /// This value defaults to 56.0 to adhere to the Material Design specifications. + final double headingRowHeight; + /// The horizontal margin between the edges of the table and the content /// in the first and last cells of each row. /// @@ -330,7 +344,9 @@ class DataTable extends StatelessWidget { final double columnSpacing; /// The data to show in each row (excluding the row that contains - /// the column headings). Must be non-null, but may be empty. + /// the column headings). + /// + /// Must be non-null, but may be empty. final List rows; // Set by the constructor to the index of the only Column that is @@ -367,8 +383,6 @@ class DataTable extends StatelessWidget { } } - static const double _headingRowHeight = 56.0; - static const double _dataRowHeight = 48.0; static const double _sortArrowPadding = 2.0; static const double _headingFontSize = 12.0; static const Duration _sortArrowAnimationDuration = Duration(milliseconds: 150); @@ -430,14 +444,14 @@ class DataTable extends StatelessWidget { } label = Container( padding: padding, - height: _headingRowHeight, + height: headingRowHeight, alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart, child: AnimatedDefaultTextStyle( style: TextStyle( // TODO(ianh): font family should match Theme; see https://github.com/flutter/flutter/issues/3116 fontWeight: FontWeight.w500, fontSize: _headingFontSize, - height: math.min(1.0, _headingRowHeight / _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), @@ -483,7 +497,7 @@ class DataTable extends StatelessWidget { } label = Container( padding: padding, - height: _dataRowHeight, + height: dataRowHeight, alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart, child: DefaultTextStyle( style: TextStyle( diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index ca1f8c0396..8f61b35038 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -70,6 +70,8 @@ class PaginatedDataTable extends StatefulWidget { this.sortColumnIndex, this.sortAscending = true, this.onSelectAll, + this.dataRowHeight = 48.0, + this.headingRowHeight = 56.0, this.horizontalMargin = 24.0, this.columnSpacing = 56.0, this.initialFirstRowIndex = 0, @@ -85,6 +87,8 @@ class PaginatedDataTable extends StatefulWidget { 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(rowsPerPage != null), @@ -135,6 +139,16 @@ class PaginatedDataTable extends StatefulWidget { /// See [DataTable.onSelectAll]. final ValueSetter onSelectAll; + /// The height of each row (excluding the row that contains column headings). + /// + /// This value is optional and defaults to 48.0 if not specified. + final double dataRowHeight; + + /// The height of the heading row. + /// + /// This value is optional and defaults to 56.0 if not specified. + final double headingRowHeight; + /// The horizontal margin between the edges of the table and the content /// in the first and last cells of each row. /// @@ -448,6 +462,8 @@ class PaginatedDataTableState extends State { sortColumnIndex: widget.sortColumnIndex, sortAscending: widget.sortAscending, onSelectAll: widget.onSelectAll, + dataRowHeight: widget.dataRowHeight, + headingRowHeight: widget.headingRowHeight, horizontalMargin: widget.horizontalMargin, columnSpacing: widget.columnSpacing, rows: _getRows(_firstRowIndex, widget.rowsPerPage), diff --git a/packages/flutter/test/material/data_table_test.dart b/packages/flutter/test/material/data_table_test.dart index bacc9671de..54c394a104 100644 --- a/packages/flutter/test/material/data_table_test.dart +++ b/packages/flutter/test/material/data_table_test.dart @@ -267,6 +267,123 @@ void main() { expect(tester.takeException(), isNull); }); + testWidgets('DataTable custom row height', (WidgetTester tester) async { + Widget buildCustomTable({ + int sortColumnIndex, + bool sortAscending = true, + double dataRowHeight = 48.0, + double headingRowHeight = 56.0, + }) { + return DataTable( + sortColumnIndex: sortColumnIndex, + sortAscending: sortAscending, + onSelectAll: (bool value) {}, + dataRowHeight: dataRowHeight, + headingRowHeight: headingRowHeight, + columns: [ + const DataColumn( + label: Text('Name'), + tooltip: 'Name', + ), + DataColumn( + label: const Text('Calories'), + tooltip: 'Calories', + numeric: true, + onSort: (int columnIndex, bool ascending) {}, + ), + ], + rows: kDesserts.map((Dessert dessert) { + return DataRow( + key: Key(dessert.name), + onSelectChanged: (bool selected) {}, + cells: [ + DataCell( + Text(dessert.name), + ), + DataCell( + Text('${dessert.calories}'), + showEditIcon: true, + onTap: () {}, + ), + ], + ); + }).toList(), + ); + } + + // DEFAULT VALUES + await tester.pumpWidget(MaterialApp( + home: Material( + child: DataTable( + onSelectAll: (bool value) {}, + columns: [ + const DataColumn( + label: Text('Name'), + tooltip: 'Name', + ), + DataColumn( + label: const Text('Calories'), + tooltip: 'Calories', + numeric: true, + onSort: (int columnIndex, bool ascending) {}, + ), + ], + rows: kDesserts.map((Dessert dessert) { + return DataRow( + key: Key(dessert.name), + onSelectChanged: (bool selected) {}, + cells: [ + DataCell( + Text(dessert.name), + ), + DataCell( + Text('${dessert.calories}'), + showEditIcon: true, + onTap: () {}, + ), + ], + ); + }).toList(), + ), + ), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Name') + ).size.height, 56.0); // This is the header row height + expect(tester.renderObject( + find.widgetWithText(Container, 'Frozen yogurt') + ).size.height, 48.0); // This is the data row height + + // CUSTOM VALUES + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomTable(headingRowHeight: 48.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Name') + ).size.height, 48.0); + + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomTable(headingRowHeight: 64.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Name') + ).size.height, 64.0); + + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomTable(dataRowHeight: 30.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Frozen yogurt') + ).size.height, 30.0); + + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomTable(dataRowHeight: 56.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Frozen yogurt') + ).size.height, 56.0); + }); + testWidgets('DataTable custom horizontal padding - checkbox', (WidgetTester tester) async { const double _defaultHorizontalMargin = 24.0; const double _defaultColumnSpacing = 56.0; diff --git a/packages/flutter/test/material/paginated_data_table_test.dart b/packages/flutter/test/material/paginated_data_table_test.dart index 43c84b4522..0cb2543b6b 100644 --- a/packages/flutter/test/material/paginated_data_table_test.dart +++ b/packages/flutter/test/material/paginated_data_table_test.dart @@ -296,6 +296,86 @@ void main() { expect(find.text('Rows per page:'), findsOneWidget); expect(tester.getTopLeft(find.text('Rows per page:')).dx, 18.0); // 14 padding in the footer row, 4 padding from the card }); + testWidgets('PaginatedDataTable custom row height', (WidgetTester tester) async { + final TestDataSource source = TestDataSource(); + + Widget buildCustomHeightPaginatedTable({ + double dataRowHeight = 48.0, + double headingRowHeight = 56.0, + }) { + return PaginatedDataTable( + header: const Text('Test table'), + source: source, + rowsPerPage: 2, + availableRowsPerPage: const [ + 2, 4, 8, 16, + ], + onRowsPerPageChanged: (int rowsPerPage) {}, + onPageChanged: (int rowIndex) {}, + columns: const [ + DataColumn(label: Text('Name')), + DataColumn(label: Text('Calories'), numeric: true), + DataColumn(label: Text('Generation')), + ], + dataRowHeight: dataRowHeight, + headingRowHeight: headingRowHeight, + ); + } + + // DEFAULT VALUES + await tester.pumpWidget(MaterialApp( + home: PaginatedDataTable( + header: const Text('Test table'), + source: source, + rowsPerPage: 2, + availableRowsPerPage: const [ + 2, 4, 8, 16, + ], + onRowsPerPageChanged: (int rowsPerPage) {}, + onPageChanged: (int rowIndex) {}, + columns: const [ + DataColumn(label: Text('Name')), + DataColumn(label: Text('Calories'), numeric: true), + DataColumn(label: Text('Generation')), + ], + ), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Name').first + ).size.height, 56.0); // This is the header row height + expect(tester.renderObject( + find.widgetWithText(Container, 'Frozen yogurt (0)').first + ).size.height, 48.0); // This is the data row height + + // CUSTOM VALUES + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomHeightPaginatedTable(headingRowHeight: 48.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Name').first + ).size.height, 48.0); + + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomHeightPaginatedTable(headingRowHeight: 64.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Name').first + ).size.height, 64.0); + + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomHeightPaginatedTable(dataRowHeight: 30.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Frozen yogurt (0)').first + ).size.height, 30.0); + + await tester.pumpWidget(MaterialApp( + home: Material(child: buildCustomHeightPaginatedTable(dataRowHeight: 56.0)), + )); + expect(tester.renderObject( + find.widgetWithText(Container, 'Frozen yogurt (0)').first + ).size.height, 56.0); + }); testWidgets('PaginatedDataTable custom horizontal padding - checkbox', (WidgetTester tester) async { const double _defaultHorizontalMargin = 24.0;