Add DataColumn.headingRowAlignment for DataTable (#144006)

fixes [[`DataTable`]  Unable to center the label of a DataColumn without side effects](https://github.com/flutter/flutter/issues/143340)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MaterialApp(
        home: Material(
          child: DataTable(
            columns: <DataColumn>[
              DataColumn(
                headingRowAlignment: MainAxisAlignment.center,
                onSort: (int columnIndex, bool ascending) {},
                label: const Text('Header'),
              ),
            ],
            sortColumnIndex: 0,
            rows: const <DataRow>[
              DataRow(
                cells: <DataCell>[
                  DataCell(Center(child: Text('Data'))),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

</details>

### Center `DataColumn.mainAxisAlignment` without sort arrow
![Screenshot 2024-03-25 at 17 13 05](https://github.com/flutter/flutter/assets/48603081/0c91d279-23e8-40d9-ab86-c08013c73666)

### Center `DataColumn.mainAxisAlignment` with sort arrow

![Screenshot 2024-03-25 at 17 11 54](https://github.com/flutter/flutter/assets/48603081/d042d02a-e7be-4f47-a90c-8a1ee0f7f5f3)
This commit is contained in:
Taha Tesser 2024-04-01 12:42:51 +03:00 committed by GitHub
parent 984c69a86f
commit 40dda2b4bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 147 additions and 1 deletions

View File

@ -42,6 +42,7 @@ class DataColumn {
this.numeric = false,
this.onSort,
this.mouseCursor,
this.headingRowAlignment,
});
/// The column heading.
@ -97,6 +98,17 @@ class DataColumn {
/// See also:
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// Defines the horizontal layout of the [label] and sort indicator in the
/// heading row.
///
/// If [headingRowAlignment] value is [MainAxisAlignment.center] and [onSort] is
/// not null, then a [SizedBox] with a width of sort arrow icon size and sort
/// arrow padding will be placed before the [label] to ensure the label is
/// centered in the column.
///
/// If null, then defaults to [MainAxisAlignment.start].
final MainAxisAlignment? headingRowAlignment;
}
/// Row configuration and cell data for a [DataTable].
@ -830,12 +842,16 @@ class DataTable extends StatelessWidget {
required bool ascending,
required MaterialStateProperty<Color?>? overlayColor,
required MouseCursor? mouseCursor,
required MainAxisAlignment headingRowAlignment,
}) {
final ThemeData themeData = Theme.of(context);
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
label = Row(
textDirection: numeric ? TextDirection.rtl : null,
mainAxisAlignment: headingRowAlignment,
children: <Widget>[
if (headingRowAlignment == MainAxisAlignment.center && onSort != null)
const SizedBox(width: _SortArrowState._arrowIconSize + _sortArrowPadding),
label,
if (onSort != null)
...<Widget>[
@ -1117,6 +1133,7 @@ class DataTable extends StatelessWidget {
ascending: sortAscending,
overlayColor: effectiveHeadingRowColor,
mouseCursor: column.mouseCursor?.resolve(headerStates) ?? dataTableTheme.headingCellCursor?.resolve(headerStates),
headingRowAlignment: column.headingRowAlignment ?? dataTableTheme.headingRowAlignment ?? MainAxisAlignment.start,
);
rowIndex = 1;
for (final DataRow row in rows) {

View File

@ -57,6 +57,7 @@ class DataTableThemeData with Diagnosticable {
this.checkboxHorizontalMargin,
this.headingCellCursor,
this.dataRowCursor,
this.headingRowAlignment,
}) : assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight),
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.'),
@ -114,6 +115,9 @@ class DataTableThemeData with Diagnosticable {
/// If specified, overrides the default value of [DataRow.mouseCursor].
final MaterialStateProperty<MouseCursor?>? dataRowCursor;
/// If specified, overrides the default value of [DataColumn.headingRowAlignment].
final MainAxisAlignment? headingRowAlignment;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
DataTableThemeData copyWith({
@ -136,6 +140,7 @@ class DataTableThemeData with Diagnosticable {
double? checkboxHorizontalMargin,
MaterialStateProperty<MouseCursor?>? headingCellCursor,
MaterialStateProperty<MouseCursor?>? dataRowCursor,
MainAxisAlignment? headingRowAlignment,
}) {
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.');
@ -157,6 +162,7 @@ class DataTableThemeData with Diagnosticable {
checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin,
headingCellCursor: headingCellCursor ?? this.headingCellCursor,
dataRowCursor: dataRowCursor ?? this.dataRowCursor,
headingRowAlignment: headingRowAlignment ?? this.headingRowAlignment,
);
}
@ -182,6 +188,7 @@ class DataTableThemeData with Diagnosticable {
checkboxHorizontalMargin: lerpDouble(a.checkboxHorizontalMargin, b.checkboxHorizontalMargin, t),
headingCellCursor: t < 0.5 ? a.headingCellCursor : b.headingCellCursor,
dataRowCursor: t < 0.5 ? a.dataRowCursor : b.dataRowCursor,
headingRowAlignment: t < 0.5 ? a.headingRowAlignment : b.headingRowAlignment,
);
}
@ -201,6 +208,7 @@ class DataTableThemeData with Diagnosticable {
checkboxHorizontalMargin,
headingCellCursor,
dataRowCursor,
headingRowAlignment,
);
@override
@ -225,7 +233,8 @@ class DataTableThemeData with Diagnosticable {
&& other.dividerThickness == dividerThickness
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin
&& other.headingCellCursor == headingCellCursor
&& other.dataRowCursor == dataRowCursor;
&& other.dataRowCursor == dataRowCursor
&& other.headingRowAlignment == headingRowAlignment;
}
@override
@ -245,6 +254,7 @@ class DataTableThemeData with Diagnosticable {
properties.add(DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('headingCellCursor', headingCellCursor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('dataRowCursor', dataRowCursor, defaultValue: null));
properties.add(EnumProperty<MainAxisAlignment>('headingRowAlignment', headingRowAlignment, defaultValue: null));
}
}

View File

@ -2363,6 +2363,60 @@ void main() {
final TextStyle? dataTextStyle = _getTextRenderObject(tester, 'Data 1').text.style;
expect(dataTextStyle, defaultTextStyle.style);
});
// This is a regression test for https://github.com/flutter/flutter/issues/143340.
testWidgets('DataColumn label can be centered', (WidgetTester tester) async {
const double horizontalMargin = 24.0;
Widget buildTable({ MainAxisAlignment? headingRowAlignment, bool sortEnabled = false }) {
return MaterialApp(
home: Material(
child: DataTable(
columns: <DataColumn>[
DataColumn(
headingRowAlignment: headingRowAlignment,
onSort: sortEnabled
? (int columnIndex, bool ascending) { }
: null,
label: const Text('Header'),
),
],
rows: const <DataRow>[
DataRow(
cells: <DataCell>[
DataCell(Text('Data')),
],
),
],
),
),
);
}
// Test mainAxisAlignment without sort arrow.
await tester.pumpWidget(buildTable());
Offset headerTopLeft = tester.getTopLeft(find.text('Header'));
expect(headerTopLeft.dx, equals(horizontalMargin));
// Test mainAxisAlignment.center without sort arrow.
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center));
Offset headerCenter = tester.getCenter(find.text('Header'));
expect(headerCenter.dx, equals(400));
// Test mainAxisAlignment with sort arrow.
await tester.pumpWidget(buildTable(sortEnabled: true));
headerTopLeft = tester.getTopLeft(find.text('Header'));
expect(headerTopLeft.dx, equals(horizontalMargin));
// Test mainAxisAlignment.center with sort arrow.
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center, sortEnabled: true));
headerCenter = tester.getCenter(find.text('Header'));
expect(headerCenter.dx, equals(400));
});
}
RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {

View File

@ -45,6 +45,7 @@ void main() {
expect(themeData.checkboxHorizontalMargin, null);
expect(themeData.headingCellCursor, null);
expect(themeData.dataRowCursor, null);
expect(themeData.headingRowAlignment, null);
const DataTableTheme theme = DataTableTheme(data: DataTableThemeData(), child: SizedBox());
expect(theme.data.decoration, null);
@ -62,6 +63,7 @@ void main() {
expect(theme.data.checkboxHorizontalMargin, null);
expect(theme.data.headingCellCursor, null);
expect(theme.data.dataRowCursor, null);
expect(theme.data.headingRowAlignment, null);
});
testWidgets('Default DataTableThemeData debugFillProperties', (WidgetTester tester) async {
@ -97,6 +99,7 @@ void main() {
checkboxHorizontalMargin: 6.0,
headingCellCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.grab),
dataRowCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.forbidden),
headingRowAlignment: MainAxisAlignment.center,
).debugFillProperties(builder);
final List<String> description = builder.properties
@ -118,6 +121,7 @@ void main() {
expect(description[11], 'checkboxHorizontalMargin: 6.0');
expect(description[12], 'headingCellCursor: WidgetStatePropertyAll(SystemMouseCursor(grab))');
expect(description[13], 'dataRowCursor: WidgetStatePropertyAll(SystemMouseCursor(forbidden))');
expect(description[14], 'headingRowAlignment: center');
});
testWidgets('DataTable is themeable', (WidgetTester tester) async {
@ -546,6 +550,67 @@ void main() {
expect(tester.getSize(_findFirstContainerFor('Data')).height, localThemeDataRowHeight);
});
// This is a regression test for https://github.com/flutter/flutter/issues/143340.
testWidgets('DataColumn label can be centered with DataTableTheme.headingRowAlignment', (WidgetTester tester) async {
const double horizontalMargin = 24.0;
Widget buildTable({ MainAxisAlignment? headingRowAlignment, bool sortEnabled = false }) {
return MaterialApp(
theme: ThemeData(
dataTableTheme: DataTableThemeData(
headingRowAlignment: headingRowAlignment,
),
),
home: Material(
child: DataTable(
columns: <DataColumn>[
DataColumn(
onSort: sortEnabled
? (int columnIndex, bool ascending) { }
: null,
label: const Text('Header'),
),
],
rows: const <DataRow>[
DataRow(
cells: <DataCell>[
DataCell(Text('Data')),
],
),
],
),
),
);
}
// Test mainAxisAlignment without sort arrow.
await tester.pumpWidget(buildTable());
Offset headerTopLeft = tester.getTopLeft(find.text('Header'));
expect(headerTopLeft.dx, equals(horizontalMargin));
// Test mainAxisAlignment.center without sort arrow.
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center));
await tester.pumpAndSettle();
Offset headerCenter = tester.getCenter(find.text('Header'));
expect(headerCenter.dx, equals(400));
// Test mainAxisAlignment with sort arrow.
await tester.pumpWidget(buildTable(sortEnabled: true));
await tester.pumpAndSettle();
headerTopLeft = tester.getTopLeft(find.text('Header'));
expect(headerTopLeft.dx, equals(horizontalMargin));
// Test mainAxisAlignment.center with sort arrow.
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center, sortEnabled: true));
await tester.pumpAndSettle();
headerCenter = tester.getCenter(find.text('Header'));
expect(headerCenter.dx, equals(400));
});
}
BoxDecoration _tableRowBoxDecoration({required WidgetTester tester, required int index}) {