Skip to content

Commit d1cf32a

Browse files
TahaTesserpull[bot]
authored andcommitted
Add DataColumn.headingRowAlignment for DataTable (flutter#144006)
fixes [[`DataTable`] Unable to center the label of a DataColumn without side effects](flutter#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)
1 parent 1c0081a commit d1cf32a

File tree

4 files changed

+147
-1
lines changed

4 files changed

+147
-1
lines changed

packages/flutter/lib/src/material/data_table.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class DataColumn {
4242
this.numeric = false,
4343
this.onSort,
4444
this.mouseCursor,
45+
this.headingRowAlignment,
4546
});
4647

4748
/// The column heading.
@@ -97,6 +98,17 @@ class DataColumn {
9798
/// See also:
9899
/// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
99100
final MaterialStateProperty<MouseCursor?>? mouseCursor;
101+
102+
/// Defines the horizontal layout of the [label] and sort indicator in the
103+
/// heading row.
104+
///
105+
/// If [headingRowAlignment] value is [MainAxisAlignment.center] and [onSort] is
106+
/// not null, then a [SizedBox] with a width of sort arrow icon size and sort
107+
/// arrow padding will be placed before the [label] to ensure the label is
108+
/// centered in the column.
109+
///
110+
/// If null, then defaults to [MainAxisAlignment.start].
111+
final MainAxisAlignment? headingRowAlignment;
100112
}
101113

102114
/// Row configuration and cell data for a [DataTable].
@@ -830,12 +842,16 @@ class DataTable extends StatelessWidget {
830842
required bool ascending,
831843
required MaterialStateProperty<Color?>? overlayColor,
832844
required MouseCursor? mouseCursor,
845+
required MainAxisAlignment headingRowAlignment,
833846
}) {
834847
final ThemeData themeData = Theme.of(context);
835848
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
836849
label = Row(
837850
textDirection: numeric ? TextDirection.rtl : null,
851+
mainAxisAlignment: headingRowAlignment,
838852
children: <Widget>[
853+
if (headingRowAlignment == MainAxisAlignment.center && onSort != null)
854+
const SizedBox(width: _SortArrowState._arrowIconSize + _sortArrowPadding),
839855
label,
840856
if (onSort != null)
841857
...<Widget>[
@@ -1117,6 +1133,7 @@ class DataTable extends StatelessWidget {
11171133
ascending: sortAscending,
11181134
overlayColor: effectiveHeadingRowColor,
11191135
mouseCursor: column.mouseCursor?.resolve(headerStates) ?? dataTableTheme.headingCellCursor?.resolve(headerStates),
1136+
headingRowAlignment: column.headingRowAlignment ?? dataTableTheme.headingRowAlignment ?? MainAxisAlignment.start,
11201137
);
11211138
rowIndex = 1;
11221139
for (final DataRow row in rows) {

packages/flutter/lib/src/material/data_table_theme.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class DataTableThemeData with Diagnosticable {
5757
this.checkboxHorizontalMargin,
5858
this.headingCellCursor,
5959
this.dataRowCursor,
60+
this.headingRowAlignment,
6061
}) : assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight),
6162
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
6263
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.'),
@@ -114,6 +115,9 @@ class DataTableThemeData with Diagnosticable {
114115
/// If specified, overrides the default value of [DataRow.mouseCursor].
115116
final MaterialStateProperty<MouseCursor?>? dataRowCursor;
116117

118+
/// If specified, overrides the default value of [DataColumn.headingRowAlignment].
119+
final MainAxisAlignment? headingRowAlignment;
120+
117121
/// Creates a copy of this object but with the given fields replaced with the
118122
/// new values.
119123
DataTableThemeData copyWith({
@@ -136,6 +140,7 @@ class DataTableThemeData with Diagnosticable {
136140
double? checkboxHorizontalMargin,
137141
MaterialStateProperty<MouseCursor?>? headingCellCursor,
138142
MaterialStateProperty<MouseCursor?>? dataRowCursor,
143+
MainAxisAlignment? headingRowAlignment,
139144
}) {
140145
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
141146
'dataRowHeight ($dataRowHeight) must not be set if dataRowMinHeight ($dataRowMinHeight) or dataRowMaxHeight ($dataRowMaxHeight) are set.');
@@ -157,6 +162,7 @@ class DataTableThemeData with Diagnosticable {
157162
checkboxHorizontalMargin: checkboxHorizontalMargin ?? this.checkboxHorizontalMargin,
158163
headingCellCursor: headingCellCursor ?? this.headingCellCursor,
159164
dataRowCursor: dataRowCursor ?? this.dataRowCursor,
165+
headingRowAlignment: headingRowAlignment ?? this.headingRowAlignment,
160166
);
161167
}
162168

@@ -182,6 +188,7 @@ class DataTableThemeData with Diagnosticable {
182188
checkboxHorizontalMargin: lerpDouble(a.checkboxHorizontalMargin, b.checkboxHorizontalMargin, t),
183189
headingCellCursor: t < 0.5 ? a.headingCellCursor : b.headingCellCursor,
184190
dataRowCursor: t < 0.5 ? a.dataRowCursor : b.dataRowCursor,
191+
headingRowAlignment: t < 0.5 ? a.headingRowAlignment : b.headingRowAlignment,
185192
);
186193
}
187194

@@ -201,6 +208,7 @@ class DataTableThemeData with Diagnosticable {
201208
checkboxHorizontalMargin,
202209
headingCellCursor,
203210
dataRowCursor,
211+
headingRowAlignment,
204212
);
205213

206214
@override
@@ -225,7 +233,8 @@ class DataTableThemeData with Diagnosticable {
225233
&& other.dividerThickness == dividerThickness
226234
&& other.checkboxHorizontalMargin == checkboxHorizontalMargin
227235
&& other.headingCellCursor == headingCellCursor
228-
&& other.dataRowCursor == dataRowCursor;
236+
&& other.dataRowCursor == dataRowCursor
237+
&& other.headingRowAlignment == headingRowAlignment;
229238
}
230239

231240
@override
@@ -245,6 +254,7 @@ class DataTableThemeData with Diagnosticable {
245254
properties.add(DoubleProperty('checkboxHorizontalMargin', checkboxHorizontalMargin, defaultValue: null));
246255
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('headingCellCursor', headingCellCursor, defaultValue: null));
247256
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>?>('dataRowCursor', dataRowCursor, defaultValue: null));
257+
properties.add(EnumProperty<MainAxisAlignment>('headingRowAlignment', headingRowAlignment, defaultValue: null));
248258
}
249259
}
250260

packages/flutter/test/material/data_table_test.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,60 @@ void main() {
23632363
final TextStyle? dataTextStyle = _getTextRenderObject(tester, 'Data 1').text.style;
23642364
expect(dataTextStyle, defaultTextStyle.style);
23652365
});
2366+
2367+
// This is a regression test for https://github.com/flutter/flutter/issues/143340.
2368+
testWidgets('DataColumn label can be centered', (WidgetTester tester) async {
2369+
const double horizontalMargin = 24.0;
2370+
2371+
Widget buildTable({ MainAxisAlignment? headingRowAlignment, bool sortEnabled = false }) {
2372+
return MaterialApp(
2373+
home: Material(
2374+
child: DataTable(
2375+
columns: <DataColumn>[
2376+
DataColumn(
2377+
headingRowAlignment: headingRowAlignment,
2378+
onSort: sortEnabled
2379+
? (int columnIndex, bool ascending) { }
2380+
: null,
2381+
label: const Text('Header'),
2382+
),
2383+
],
2384+
rows: const <DataRow>[
2385+
DataRow(
2386+
cells: <DataCell>[
2387+
DataCell(Text('Data')),
2388+
],
2389+
),
2390+
],
2391+
),
2392+
),
2393+
);
2394+
}
2395+
2396+
// Test mainAxisAlignment without sort arrow.
2397+
await tester.pumpWidget(buildTable());
2398+
2399+
Offset headerTopLeft = tester.getTopLeft(find.text('Header'));
2400+
expect(headerTopLeft.dx, equals(horizontalMargin));
2401+
2402+
// Test mainAxisAlignment.center without sort arrow.
2403+
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center));
2404+
2405+
Offset headerCenter = tester.getCenter(find.text('Header'));
2406+
expect(headerCenter.dx, equals(400));
2407+
2408+
// Test mainAxisAlignment with sort arrow.
2409+
await tester.pumpWidget(buildTable(sortEnabled: true));
2410+
2411+
headerTopLeft = tester.getTopLeft(find.text('Header'));
2412+
expect(headerTopLeft.dx, equals(horizontalMargin));
2413+
2414+
// Test mainAxisAlignment.center with sort arrow.
2415+
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center, sortEnabled: true));
2416+
2417+
headerCenter = tester.getCenter(find.text('Header'));
2418+
expect(headerCenter.dx, equals(400));
2419+
});
23662420
}
23672421

23682422
RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {

packages/flutter/test/material/data_table_theme_test.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ void main() {
4545
expect(themeData.checkboxHorizontalMargin, null);
4646
expect(themeData.headingCellCursor, null);
4747
expect(themeData.dataRowCursor, null);
48+
expect(themeData.headingRowAlignment, null);
4849

4950
const DataTableTheme theme = DataTableTheme(data: DataTableThemeData(), child: SizedBox());
5051
expect(theme.data.decoration, null);
@@ -62,6 +63,7 @@ void main() {
6263
expect(theme.data.checkboxHorizontalMargin, null);
6364
expect(theme.data.headingCellCursor, null);
6465
expect(theme.data.dataRowCursor, null);
66+
expect(theme.data.headingRowAlignment, null);
6567
});
6668

6769
testWidgets('Default DataTableThemeData debugFillProperties', (WidgetTester tester) async {
@@ -97,6 +99,7 @@ void main() {
9799
checkboxHorizontalMargin: 6.0,
98100
headingCellCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.grab),
99101
dataRowCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.forbidden),
102+
headingRowAlignment: MainAxisAlignment.center,
100103
).debugFillProperties(builder);
101104

102105
final List<String> description = builder.properties
@@ -118,6 +121,7 @@ void main() {
118121
expect(description[11], 'checkboxHorizontalMargin: 6.0');
119122
expect(description[12], 'headingCellCursor: WidgetStatePropertyAll(SystemMouseCursor(grab))');
120123
expect(description[13], 'dataRowCursor: WidgetStatePropertyAll(SystemMouseCursor(forbidden))');
124+
expect(description[14], 'headingRowAlignment: center');
121125
});
122126

123127
testWidgets('DataTable is themeable', (WidgetTester tester) async {
@@ -546,6 +550,67 @@ void main() {
546550

547551
expect(tester.getSize(_findFirstContainerFor('Data')).height, localThemeDataRowHeight);
548552
});
553+
554+
// This is a regression test for https://github.com/flutter/flutter/issues/143340.
555+
testWidgets('DataColumn label can be centered with DataTableTheme.headingRowAlignment', (WidgetTester tester) async {
556+
const double horizontalMargin = 24.0;
557+
558+
Widget buildTable({ MainAxisAlignment? headingRowAlignment, bool sortEnabled = false }) {
559+
return MaterialApp(
560+
theme: ThemeData(
561+
dataTableTheme: DataTableThemeData(
562+
headingRowAlignment: headingRowAlignment,
563+
),
564+
),
565+
home: Material(
566+
child: DataTable(
567+
columns: <DataColumn>[
568+
DataColumn(
569+
onSort: sortEnabled
570+
? (int columnIndex, bool ascending) { }
571+
: null,
572+
label: const Text('Header'),
573+
),
574+
],
575+
rows: const <DataRow>[
576+
DataRow(
577+
cells: <DataCell>[
578+
DataCell(Text('Data')),
579+
],
580+
),
581+
],
582+
),
583+
),
584+
);
585+
}
586+
587+
// Test mainAxisAlignment without sort arrow.
588+
await tester.pumpWidget(buildTable());
589+
590+
Offset headerTopLeft = tester.getTopLeft(find.text('Header'));
591+
expect(headerTopLeft.dx, equals(horizontalMargin));
592+
593+
// Test mainAxisAlignment.center without sort arrow.
594+
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center));
595+
await tester.pumpAndSettle();
596+
597+
Offset headerCenter = tester.getCenter(find.text('Header'));
598+
expect(headerCenter.dx, equals(400));
599+
600+
// Test mainAxisAlignment with sort arrow.
601+
await tester.pumpWidget(buildTable(sortEnabled: true));
602+
await tester.pumpAndSettle();
603+
604+
headerTopLeft = tester.getTopLeft(find.text('Header'));
605+
expect(headerTopLeft.dx, equals(horizontalMargin));
606+
607+
// Test mainAxisAlignment.center with sort arrow.
608+
await tester.pumpWidget(buildTable(headingRowAlignment: MainAxisAlignment.center, sortEnabled: true));
609+
await tester.pumpAndSettle();
610+
611+
headerCenter = tester.getCenter(find.text('Header'));
612+
expect(headerCenter.dx, equals(400));
613+
});
549614
}
550615

551616
BoxDecoration _tableRowBoxDecoration({required WidgetTester tester, required int index}) {

0 commit comments

Comments
 (0)