mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
PaginatedDataTable improvements (#131374)
- slightly improved assert message when row cell counts don't match column count. - more breadcrumbs in API documentation. more documentation in general. - added more documentation for the direction of the "ascending" arrow. - two samples for PaginatedDataTable. - make PaginatedDataTable support hot reloading across changes to the number of columns. - introduce matrix3MoreOrLessEquals. An earlier version of this PR used it in tests, but eventually it was not needed. The function seems useful to keep though.
This commit is contained in:
parent
668a002280
commit
ccdf826466
@ -0,0 +1,86 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample for [PaginatedDataTable].
|
||||
|
||||
class MyDataSource extends DataTableSource {
|
||||
@override
|
||||
int get rowCount => 3;
|
||||
|
||||
@override
|
||||
DataRow? getRow(int index) {
|
||||
switch (index) {
|
||||
case 0: return const DataRow(
|
||||
cells: <DataCell>[
|
||||
DataCell(Text('Sarah')),
|
||||
DataCell(Text('19')),
|
||||
DataCell(Text('Student')),
|
||||
],
|
||||
);
|
||||
case 1: return const DataRow(
|
||||
cells: <DataCell>[
|
||||
DataCell(Text('Janine')),
|
||||
DataCell(Text('43')),
|
||||
DataCell(Text('Professor')),
|
||||
],
|
||||
);
|
||||
case 2: return const DataRow(
|
||||
cells: <DataCell>[
|
||||
DataCell(Text('William')),
|
||||
DataCell(Text('27')),
|
||||
DataCell(Text('Associate Professor')),
|
||||
],
|
||||
);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
}
|
||||
|
||||
final DataTableSource dataSource = MyDataSource();
|
||||
|
||||
void main() => runApp(const DataTableExampleApp());
|
||||
|
||||
class DataTableExampleApp extends StatelessWidget {
|
||||
const DataTableExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: DataTableExample(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DataTableExample extends StatelessWidget {
|
||||
const DataTableExample({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PaginatedDataTable(
|
||||
columns: const <DataColumn>[
|
||||
DataColumn(
|
||||
label: Text('Name'),
|
||||
),
|
||||
DataColumn(
|
||||
label: Text('Age'),
|
||||
),
|
||||
DataColumn(
|
||||
label: Text('Role'),
|
||||
),
|
||||
],
|
||||
source: dataSource,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample for [PaginatedDataTable].
|
||||
|
||||
class MyDataSource extends DataTableSource {
|
||||
static const List<int> _displayIndexToRawIndex = <int>[ 0, 3, 4, 5, 6 ];
|
||||
|
||||
late List<List<Comparable<Object>>> sortedData;
|
||||
void setData(List<List<Comparable<Object>>> rawData, int sortColumn, bool sortAscending) {
|
||||
sortedData = rawData.toList()..sort((List<Comparable<Object>> a, List<Comparable<Object>> b) {
|
||||
final Comparable<Object> cellA = a[_displayIndexToRawIndex[sortColumn]];
|
||||
final Comparable<Object> cellB = b[_displayIndexToRawIndex[sortColumn]];
|
||||
return cellA.compareTo(cellB) * (sortAscending ? 1 : -1);
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
int get rowCount => sortedData.length;
|
||||
|
||||
static DataCell cellFor(Object data) {
|
||||
String value;
|
||||
if (data is DateTime) {
|
||||
value = '${data.year}-${data.month.toString().padLeft(2, '0')}-${data.day.toString().padLeft(2, '0')}';
|
||||
} else {
|
||||
value = data.toString();
|
||||
}
|
||||
return DataCell(Text(value));
|
||||
}
|
||||
|
||||
@override
|
||||
DataRow? getRow(int index) {
|
||||
return DataRow.byIndex(
|
||||
index: sortedData[index][0] as int,
|
||||
cells: <DataCell>[
|
||||
cellFor('S${sortedData[index][1]}E${sortedData[index][2].toString().padLeft(2, '0')}'),
|
||||
cellFor(sortedData[index][3]),
|
||||
cellFor(sortedData[index][4]),
|
||||
cellFor(sortedData[index][5]),
|
||||
cellFor(sortedData[index][6]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => 0;
|
||||
}
|
||||
|
||||
void main() => runApp(const DataTableExampleApp());
|
||||
|
||||
class DataTableExampleApp extends StatelessWidget {
|
||||
const DataTableExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: DataTableExample(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DataTableExample extends StatefulWidget {
|
||||
const DataTableExample({super.key});
|
||||
|
||||
@override
|
||||
State<DataTableExample> createState() => _DataTableExampleState();
|
||||
}
|
||||
|
||||
class _DataTableExampleState extends State<DataTableExample> {
|
||||
final MyDataSource dataSource = MyDataSource()
|
||||
..setData(episodes, 0, true);
|
||||
|
||||
int _columnIndex = 0;
|
||||
bool _columnAscending = true;
|
||||
|
||||
void _sort(int columnIndex, bool ascending) {
|
||||
setState(() {
|
||||
_columnIndex = columnIndex;
|
||||
_columnAscending = ascending;
|
||||
dataSource.setData(episodes, _columnIndex, _columnAscending);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PaginatedDataTable(
|
||||
sortColumnIndex: _columnIndex,
|
||||
sortAscending: _columnAscending,
|
||||
columns: <DataColumn>[
|
||||
DataColumn(
|
||||
label: const Text('Episode'),
|
||||
onSort: _sort,
|
||||
),
|
||||
DataColumn(
|
||||
label: const Text('Title'),
|
||||
onSort: _sort,
|
||||
),
|
||||
DataColumn(
|
||||
label: const Text('Director'),
|
||||
onSort: _sort,
|
||||
),
|
||||
DataColumn(
|
||||
label: const Text('Writer(s)'),
|
||||
onSort: _sort,
|
||||
),
|
||||
DataColumn(
|
||||
label: const Text('Air Date'),
|
||||
onSort: _sort,
|
||||
),
|
||||
],
|
||||
source: dataSource,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final List<List<Comparable<Object>>> episodes = <List<Comparable<Object>>>[
|
||||
<Comparable<Object>>[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
'Strange New Worlds',
|
||||
'Akiva Goldsman',
|
||||
'Akiva Goldsman, Alex Kurtzman, Jenny Lumet',
|
||||
DateTime(2022, 5, 5),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
'Children of the Comet',
|
||||
'Maja Vrvilo',
|
||||
'Henry Alonso Myers, Sarah Tarkoff',
|
||||
DateTime(2022, 5, 12),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
3,
|
||||
1,
|
||||
3,
|
||||
'Ghosts of Illyria',
|
||||
'Leslie Hope',
|
||||
'Akela Cooper, Bill Wolkoff',
|
||||
DateTime(2022, 5, 19),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
4,
|
||||
1,
|
||||
4,
|
||||
'Memento Mori',
|
||||
'Dan Liu',
|
||||
'Davy Perez, Beau DeMayo',
|
||||
DateTime(2022, 5, 26),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
5,
|
||||
1,
|
||||
5,
|
||||
'Spock Amok',
|
||||
'Rachel Leiterman',
|
||||
'Henry Alonso Myers, Robin Wasserman',
|
||||
DateTime(2022, 6, 2),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
6,
|
||||
1,
|
||||
6,
|
||||
'Lift Us Where Suffering Cannot Reach',
|
||||
'Andi Armaganian',
|
||||
'Robin Wasserman, Bill Wolkoff',
|
||||
DateTime(2022, 6, 9),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
7,
|
||||
1,
|
||||
7,
|
||||
'The Serene Squall',
|
||||
'Sydney Freeland',
|
||||
'Beau DeMayo, Sarah Tarkoff',
|
||||
DateTime(2022, 6, 16),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
8,
|
||||
1,
|
||||
8,
|
||||
'The Elysian Kingdom',
|
||||
'Amanda Row',
|
||||
'Akela Cooper, Onitra Johnson',
|
||||
DateTime(2022, 6, 23),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
9,
|
||||
1,
|
||||
9,
|
||||
'All Those Who Wander',
|
||||
'Christopher J. Byrne',
|
||||
'Davy Perez',
|
||||
DateTime(2022, 6, 30),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
10,
|
||||
2,
|
||||
10,
|
||||
'A Quality of Mercy',
|
||||
'Chris Fisher',
|
||||
'Henry Alonso Myers, Akiva Goldsman',
|
||||
DateTime(2022, 7, 7),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
11,
|
||||
2,
|
||||
1,
|
||||
'The Broken Circle',
|
||||
'Chris Fisher',
|
||||
'Henry Alonso Myers, Akiva Goldsman',
|
||||
DateTime(2023, 6, 15),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
12,
|
||||
2,
|
||||
2,
|
||||
'Ad Astra per Aspera',
|
||||
'Valerie Weiss',
|
||||
'Dana Horgan',
|
||||
DateTime(2023, 6, 22),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
13,
|
||||
2,
|
||||
3,
|
||||
'Tomorrow and Tomorrow and Tomorrow',
|
||||
'Amanda Row',
|
||||
'David Reed',
|
||||
DateTime(2023, 6, 29),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
14,
|
||||
2,
|
||||
4,
|
||||
'Among the Lotus Eaters',
|
||||
'Eduardo Sánchez',
|
||||
'Kirsten Beyer, Davy Perez',
|
||||
DateTime(2023, 7, 6),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
15,
|
||||
2,
|
||||
5,
|
||||
'Charades',
|
||||
'Jordan Canning',
|
||||
'Kathryn Lyn, Henry Alonso Myers',
|
||||
DateTime(2023, 7, 13),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
16,
|
||||
2,
|
||||
6,
|
||||
'Lost in Translation',
|
||||
'Dan Liu',
|
||||
'Onitra Johnson, David Reed',
|
||||
DateTime(2023, 7, 20),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
17,
|
||||
2,
|
||||
7,
|
||||
'Those Old Scientists',
|
||||
'Jonathan Frakes',
|
||||
'Kathryn Lyn, Bill Wolkoff',
|
||||
DateTime(2023, 7, 22),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
18,
|
||||
2,
|
||||
8,
|
||||
'Under the Cloak of War',
|
||||
'',
|
||||
'Davy Perez',
|
||||
DateTime(2023, 7, 27),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
19,
|
||||
2,
|
||||
9,
|
||||
'Subspace Rhapsody',
|
||||
'',
|
||||
'Dana Horgan, Bill Wolkoff',
|
||||
DateTime(2023, 8, 3),
|
||||
],
|
||||
<Comparable<Object>>[
|
||||
20,
|
||||
2,
|
||||
10,
|
||||
'Hegemony',
|
||||
'',
|
||||
'Henry Alonso Myers',
|
||||
DateTime(2023, 8, 10),
|
||||
],
|
||||
];
|
@ -0,0 +1,13 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_api_samples/material/paginated_data_table/paginated_data_table.0.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('PaginatedDataTable 0', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const example.DataTableExampleApp());
|
||||
expect(find.text('Associate Professor'), findsOneWidget);
|
||||
});
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/material/paginated_data_table/paginated_data_table.1.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('PaginatedDataTable 1', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const example.DataTableExampleApp());
|
||||
expect(find.text('Strange New Worlds'), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.arrow_upward).at(1));
|
||||
await tester.pump();
|
||||
expect(find.text('Strange New Worlds'), findsNothing);
|
||||
await tester.tap(find.byIcon(Icons.chevron_right));
|
||||
await tester.pump();
|
||||
expect(find.text('Strange New Worlds'), findsOneWidget);
|
||||
await tester.tap(find.byIcon(Icons.arrow_upward).at(1));
|
||||
await tester.pump();
|
||||
expect(find.text('Strange New Worlds'), findsNothing);
|
||||
});
|
||||
}
|
@ -444,7 +444,7 @@ class DataTable extends StatelessWidget {
|
||||
this.clipBehavior = Clip.none,
|
||||
}) : assert(columns.isNotEmpty),
|
||||
assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
|
||||
assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
|
||||
assert(!rows.any((DataRow row) => row.cells.length != columns.length), 'All rows must have the same number of cells as there are header cells (${columns.length})'),
|
||||
assert(dividerThickness == null || dividerThickness >= 0),
|
||||
assert(dataRowMinHeight == null || dataRowMaxHeight == null || dataRowMaxHeight >= dataRowMinHeight),
|
||||
assert(dataRowHeight == null || (dataRowMinHeight == null && dataRowMaxHeight == null),
|
||||
@ -467,6 +467,8 @@ class DataTable extends StatelessWidget {
|
||||
///
|
||||
/// When this is null, it implies that the table's sort order does
|
||||
/// not correspond to any of the columns.
|
||||
///
|
||||
/// The direction of the sort is specified using [sortAscending].
|
||||
final int? sortColumnIndex;
|
||||
|
||||
/// Whether the column mentioned in [sortColumnIndex], if any, is sorted
|
||||
@ -479,6 +481,8 @@ class DataTable extends StatelessWidget {
|
||||
/// If false, the order is descending (meaning the rows with the
|
||||
/// smallest values for the current sort column are last in the
|
||||
/// table).
|
||||
///
|
||||
/// Ascending order is represented by an upwards-facing arrow.
|
||||
final bool sortAscending;
|
||||
|
||||
/// Invoked when the user selects or unselects every row, using the
|
||||
|
@ -18,21 +18,32 @@ import 'data_table.dart';
|
||||
///
|
||||
/// DataTableSource objects are expected to be long-lived, not recreated with
|
||||
/// each build.
|
||||
///
|
||||
/// If a [DataTableSource] is used with a [PaginatedDataTable] that supports
|
||||
/// sortable columns (see [DataColumn.onSort] and
|
||||
/// [PaginatedDataTable.sortColumnIndex]), the rows reported by the data source
|
||||
/// must be reported in the sorted order.
|
||||
abstract class DataTableSource extends ChangeNotifier {
|
||||
/// Called to obtain the data about a particular row.
|
||||
///
|
||||
/// Rows should be keyed so that state can be maintained when the data source
|
||||
/// is sorted (e.g. in response to [DataColumn.onSort]). Keys should be
|
||||
/// consistent for a given [DataRow] regardless of the sort order (i.e. the
|
||||
/// key represents the data's identity, not the row position).
|
||||
///
|
||||
/// The [DataRow.byIndex] constructor provides a convenient way to construct
|
||||
/// [DataRow] objects for this callback's purposes without having to worry about
|
||||
/// independently keying each row.
|
||||
/// [DataRow] objects for this method's purposes without having to worry about
|
||||
/// independently keying each row. The index passed to that constructor is the
|
||||
/// index of the underlying data, which is different than the `index`
|
||||
/// parameter for [getRow], which represents the _sorted_ position.
|
||||
///
|
||||
/// If the given index does not correspond to a row, or if no data is yet
|
||||
/// available for a row, then return null. The row will be left blank and a
|
||||
/// loading indicator will be displayed over the table. Once data is available
|
||||
/// or once it is firmly established that the row index in question is beyond
|
||||
/// the end of the table, call [notifyListeners].
|
||||
/// the end of the table, call [notifyListeners]. (See [rowCount].)
|
||||
///
|
||||
/// Data returned from this method must be consistent for the lifetime of the
|
||||
/// object. If the row count changes, then a new delegate must be provided.
|
||||
/// If the underlying data changes, call [notifyListeners].
|
||||
DataRow? getRow(int index);
|
||||
|
||||
/// Called to obtain the number of rows to tell the user are available.
|
||||
@ -58,5 +69,7 @@ abstract class DataTableSource extends ChangeNotifier {
|
||||
/// Called to obtain the number of rows that are currently selected.
|
||||
///
|
||||
/// If the selected row count changes, call [notifyListeners].
|
||||
///
|
||||
/// Selected rows are those whose [DataRow.selected] property is set to true.
|
||||
int get selectedRowCount;
|
||||
}
|
||||
|
@ -31,6 +31,23 @@ import 'theme.dart';
|
||||
/// If the [key] is a [PageStorageKey], the [initialFirstRowIndex] is persisted
|
||||
/// to [PageStorage].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
///
|
||||
/// This sample shows how to display a [DataTable] with three columns: name,
|
||||
/// age, and role. The columns are defined by three [DataColumn] objects. The
|
||||
/// table contains three rows of data for three example users, the data for
|
||||
/// which is defined by three [DataRow] objects.
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/paginated_data_table/paginated_data_table.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
///
|
||||
/// This example shows how paginated data tables can supported sorted data.
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/paginated_data_table/paginated_data_table.1.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DataTable], which is not paginated.
|
||||
@ -142,13 +159,15 @@ class PaginatedDataTable extends StatefulWidget {
|
||||
|
||||
/// The current primary sort key's column.
|
||||
///
|
||||
/// See [DataTable.sortColumnIndex].
|
||||
/// See [DataTable.sortColumnIndex] for details.
|
||||
///
|
||||
/// The direction of the sort is specified using [sortAscending].
|
||||
final int? sortColumnIndex;
|
||||
|
||||
/// Whether the column mentioned in [sortColumnIndex], if any, is sorted
|
||||
/// in ascending order.
|
||||
///
|
||||
/// See [DataTable.sortAscending].
|
||||
/// See [DataTable.sortAscending] for details.
|
||||
final bool sortAscending;
|
||||
|
||||
/// Invoked when the user selects or unselects every row, using the
|
||||
@ -297,10 +316,27 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
|
||||
if (oldWidget.source != widget.source) {
|
||||
oldWidget.source.removeListener(_handleDataSourceChanged);
|
||||
widget.source.addListener(_handleDataSourceChanged);
|
||||
_handleDataSourceChanged();
|
||||
_updateCaches();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
super.reassemble();
|
||||
// This function is called during hot reload.
|
||||
//
|
||||
// Normally, if the data source changes, it would notify its listeners and
|
||||
// thus trigger _handleDataSourceChanged(), which clears the row cache and
|
||||
// causes the widget to rebuild.
|
||||
//
|
||||
// During a hot reload, though, a data source can change in ways that will
|
||||
// invalidate the row cache (e.g. adding or removing columns) without ever
|
||||
// triggering a notification, leaving the PaginatedDataTable in an invalid
|
||||
// state. This method handles this case by clearing the cache any time the
|
||||
// widget is involved in a hot reload.
|
||||
_updateCaches();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.source.removeListener(_handleDataSourceChanged);
|
||||
@ -308,12 +344,14 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
|
||||
}
|
||||
|
||||
void _handleDataSourceChanged() {
|
||||
setState(() {
|
||||
_rowCount = widget.source.rowCount;
|
||||
_rowCountApproximate = widget.source.isRowCountApproximate;
|
||||
_selectedRowCount = widget.source.selectedRowCount;
|
||||
_rows.clear();
|
||||
});
|
||||
setState(_updateCaches);
|
||||
}
|
||||
|
||||
void _updateCaches() {
|
||||
_rowCount = widget.source.rowCount;
|
||||
_rowCountApproximate = widget.source.isRowCountApproximate;
|
||||
_selectedRowCount = widget.source.selectedRowCount;
|
||||
_rows.clear();
|
||||
}
|
||||
|
||||
/// Ensures that the given row is visible.
|
||||
|
@ -469,11 +469,11 @@ void main() {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Material(child: buildTable()),
|
||||
));
|
||||
// The `tester.widget` ensures that there is exactly one upward arrow.
|
||||
final Finder iconFinder = find.descendant(
|
||||
of: find.byType(DataTable),
|
||||
matching: find.widgetWithIcon(Transform, Icons.arrow_upward),
|
||||
);
|
||||
// The `tester.widget` ensures that there is exactly one upward arrow.
|
||||
Transform transformOfArrow = tester.widget<Transform>(iconFinder);
|
||||
expect(
|
||||
transformOfArrow.transform.getRotation(),
|
||||
@ -521,11 +521,11 @@ void main() {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Material(child: buildTable()),
|
||||
));
|
||||
// The `tester.widget` ensures that there is exactly one upward arrow.
|
||||
final Finder iconFinder = find.descendant(
|
||||
of: find.byType(DataTable),
|
||||
matching: find.widgetWithIcon(Transform, Icons.arrow_upward),
|
||||
);
|
||||
// The `tester.widget` ensures that there is exactly one upward arrow.
|
||||
Transform transformOfArrow = tester.widget<Transform>(iconFinder);
|
||||
expect(
|
||||
transformOfArrow.transform.getRotation(),
|
||||
@ -574,11 +574,11 @@ void main() {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Material(child: buildTable()),
|
||||
));
|
||||
// The `tester.widget` ensures that there is exactly one upward arrow.
|
||||
final Finder iconFinder = find.descendant(
|
||||
of: find.byType(DataTable),
|
||||
matching: find.widgetWithIcon(Transform, Icons.arrow_upward),
|
||||
);
|
||||
// The `tester.widget` ensures that there is exactly one upward arrow.
|
||||
Transform transformOfArrow = tester.widget<Transform>(iconFinder);
|
||||
expect(
|
||||
transformOfArrow.transform.getRotation(),
|
||||
|
@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:matcher/expect.dart';
|
||||
import 'package:matcher/src/expect/async_matcher.dart'; // ignore: implementation_imports
|
||||
import 'package:vector_math/vector_math_64.dart' show Matrix3;
|
||||
|
||||
import '_matchers_io.dart' if (dart.library.html) '_matchers_web.dart' show MatchesGoldenFile, captureImage;
|
||||
import 'accessibility.dart';
|
||||
@ -345,10 +346,24 @@ Matcher rectMoreOrLessEquals(Rect value, { double epsilon = precisionErrorTolera
|
||||
///
|
||||
/// * [moreOrLessEquals], which is for [double]s.
|
||||
/// * [offsetMoreOrLessEquals], which is for [Offset]s.
|
||||
/// * [matrix3MoreOrLessEquals], which is for [Matrix3]s.
|
||||
Matcher matrixMoreOrLessEquals(Matrix4 value, { double epsilon = precisionErrorTolerance }) {
|
||||
return _IsWithinDistance<Matrix4>(_matrixDistance, value, epsilon);
|
||||
}
|
||||
|
||||
/// Asserts that two [Matrix3]s are equal, within some tolerated error.
|
||||
///
|
||||
/// {@macro flutter.flutter_test.moreOrLessEquals}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [moreOrLessEquals], which is for [double]s.
|
||||
/// * [offsetMoreOrLessEquals], which is for [Offset]s.
|
||||
/// * [matrixMoreOrLessEquals], which is for [Matrix4]s.
|
||||
Matcher matrix3MoreOrLessEquals(Matrix3 value, { double epsilon = precisionErrorTolerance }) {
|
||||
return _IsWithinDistance<Matrix3>(_matrix3Distance, value, epsilon);
|
||||
}
|
||||
|
||||
/// Asserts that two [Offset]s are equal, within some tolerated error.
|
||||
///
|
||||
/// {@macro flutter.flutter_test.moreOrLessEquals}
|
||||
@ -1443,6 +1458,14 @@ double _matrixDistance(Matrix4 a, Matrix4 b) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
double _matrix3Distance(Matrix3 a, Matrix3 b) {
|
||||
double delta = 0.0;
|
||||
for (int i = 0; i < 9; i += 1) {
|
||||
delta = math.max<double>((a[i] - b[i]).abs(), delta);
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
double _sizeDistance(Size a, Size b) {
|
||||
// TODO(a14n): remove ignore when lint is updated, https://github.com/dart-lang/linter/issues/1843
|
||||
// ignore: unnecessary_parenthesis
|
||||
|
@ -10,6 +10,7 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' show Matrix3;
|
||||
|
||||
/// Class that makes it easy to mock common toStringDeep behavior.
|
||||
class _MockToStringDeep {
|
||||
@ -251,6 +252,35 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('matrix3MoreOrLessEquals', () {
|
||||
expect(
|
||||
Matrix3.rotationZ(math.pi),
|
||||
matrix3MoreOrLessEquals(Matrix3.fromList(<double>[
|
||||
-1, 0, 0,
|
||||
0, -1, 0,
|
||||
0, 0, 1,
|
||||
]))
|
||||
);
|
||||
|
||||
expect(
|
||||
Matrix3.rotationZ(math.pi),
|
||||
matrix3MoreOrLessEquals(Matrix3.fromList(<double>[
|
||||
-2, 0, 0,
|
||||
0, -2, 0,
|
||||
0, 0, 1,
|
||||
]), epsilon: 2)
|
||||
);
|
||||
|
||||
expect(
|
||||
Matrix3.rotationZ(math.pi),
|
||||
isNot(matrix3MoreOrLessEquals(Matrix3.fromList(<double>[
|
||||
-2, 0, 0,
|
||||
0, -2, 0,
|
||||
0, 0, 1,
|
||||
])))
|
||||
);
|
||||
});
|
||||
|
||||
test('rectMoreOrLessEquals', () {
|
||||
expect(
|
||||
const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
|
||||
|
Loading…
Reference in New Issue
Block a user