Skip to content

Commit 5fe7854

Browse files
authored
[DataTable]: Add ability to only select row using checkbox (#105123)
1 parent 032dc54 commit 5fe7854

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class DataRow {
100100
this.selected = false,
101101
this.onSelectChanged,
102102
this.onLongPress,
103+
this.selectableOnGestures = true,
103104
this.color,
104105
required this.cells,
105106
}) : assert(cells != null);
@@ -113,6 +114,7 @@ class DataRow {
113114
this.selected = false,
114115
this.onSelectChanged,
115116
this.onLongPress,
117+
this.selectableOnGestures = true,
116118
this.color,
117119
required this.cells,
118120
}) : assert(cells != null),
@@ -163,6 +165,18 @@ class DataRow {
163165
/// Otherwise, the checkbox, if present, will not be checked.
164166
final bool selected;
165167

168+
/// Whether the row can be selected by using gestures such as
169+
/// tap or long press.
170+
///
171+
/// If the value is set to false and [onSelectChanged] is non-null,
172+
/// the row can be only selected by toggling the checkbox.
173+
///
174+
/// If the value is set to false and [onLongPress] is non-null,
175+
/// [onLongPress] callback won't be called.
176+
///
177+
/// This value is true by default.
178+
final bool selectableOnGestures;
179+
166180
/// The data for this row.
167181
///
168182
/// There must be exactly as many cells as there are columns in the
@@ -807,6 +821,7 @@ class DataTable extends StatelessWidget {
807821
required GestureTapCancelCallback? onTapCancel,
808822
required MaterialStateProperty<Color?>? overlayColor,
809823
required GestureLongPressCallback? onRowLongPress,
824+
required bool selectableOnGestures,
810825
}) {
811826
final ThemeData themeData = Theme.of(context);
812827
final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
@@ -852,7 +867,7 @@ class DataTable extends StatelessWidget {
852867
overlayColor: overlayColor,
853868
child: label,
854869
);
855-
} else if (onSelectChanged != null || onRowLongPress != null) {
870+
} else if (selectableOnGestures && (onSelectChanged != null || onRowLongPress != null)) {
856871
label = TableRowInkWell(
857872
onTap: onSelectChanged,
858873
onLongPress: onRowLongPress,
@@ -1031,6 +1046,7 @@ class DataTable extends StatelessWidget {
10311046
onSelectChanged: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
10321047
overlayColor: row.color ?? effectiveDataRowColor,
10331048
onRowLongPress: row.onLongPress,
1049+
selectableOnGestures: row.selectableOnGestures,
10341050
);
10351051
rowIndex += 1;
10361052
}

packages/flutter/test/material/data_table_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,84 @@ void main() {
293293
log.clear();
294294
});
295295

296+
testWidgets('selectableOnGestures disables gestures on row', (WidgetTester tester) async {
297+
final List<String> log = <String>[];
298+
299+
Widget buildTable({ int? sortColumnIndex, bool sortAscending = true }) {
300+
return DataTable(
301+
sortColumnIndex: sortColumnIndex,
302+
sortAscending: sortAscending,
303+
onSelectAll: (bool? value) {
304+
log.add('select-all: $value');
305+
},
306+
columns: <DataColumn>[
307+
const DataColumn(
308+
label: Text('Name'),
309+
tooltip: 'Name',
310+
),
311+
DataColumn(
312+
label: const Text('Calories'),
313+
tooltip: 'Calories',
314+
numeric: true,
315+
onSort: (int columnIndex, bool ascending) {
316+
log.add('column-sort: $columnIndex $ascending');
317+
},
318+
),
319+
],
320+
rows: kDesserts.map<DataRow>((Dessert dessert) {
321+
return DataRow(
322+
key: ValueKey<String>(dessert.name),
323+
selectableOnGestures: false,
324+
onSelectChanged: (bool? selected) {
325+
log.add('row-selected: ${dessert.name}');
326+
},
327+
onLongPress: () {
328+
log.add('onLongPress: ${dessert.name}');
329+
},
330+
cells: <DataCell>[
331+
DataCell(
332+
Text(dessert.name),
333+
),
334+
DataCell(
335+
Text('${dessert.calories}'),
336+
showEditIcon: true,
337+
onTap: () {
338+
log.add('cell-tap: ${dessert.calories}');
339+
},
340+
onDoubleTap: () {
341+
log.add('cell-doubleTap: ${dessert.calories}');
342+
},
343+
onLongPress: () {
344+
log.add('cell-longPress: ${dessert.calories}');
345+
},
346+
onTapCancel: () {
347+
log.add('cell-tapCancel: ${dessert.calories}');
348+
},
349+
onTapDown: (TapDownDetails details) {
350+
log.add('cell-tapDown: ${dessert.calories}');
351+
},
352+
),
353+
],
354+
);
355+
}).toList(),
356+
);
357+
}
358+
359+
await tester.pumpWidget(MaterialApp(
360+
home: Material(child: buildTable()),
361+
));
362+
363+
await tester.tap(find.text('KitKat'));
364+
expect(log.length, 0);
365+
366+
await tester.longPress(find.text('KitKat'));
367+
expect(log.length, 0);
368+
369+
await tester.tap(find.byType(Checkbox).last);
370+
expect(log, <String>['row-selected: KitKat']);
371+
log.clear();
372+
});
373+
296374
testWidgets('DataTable overflow test - header', (WidgetTester tester) async {
297375
await tester.pumpWidget(
298376
MaterialApp(

0 commit comments

Comments
 (0)