Advertisement
Guest User

pdt

a guest
May 20th, 2020
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.77 KB | None | 0 0
  1. // Copyright 2014 The Flutter Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. import 'dart:math' as math;
  6.  
  7. import 'package:flutter/widgets.dart';
  8. import 'package:flutter/rendering.dart';
  9. import 'package:flutter/gestures.dart' show DragStartBehavior;
  10.  
  11. import 'button_bar.dart';
  12. import 'card.dart';
  13. import 'constants.dart';
  14. import 'data_table.dart';
  15. import 'data_table_source.dart';
  16. import 'debug.dart';
  17. import 'dropdown.dart';
  18. import 'icon_button.dart';
  19. import 'icons.dart';
  20. import 'ink_decoration.dart';
  21. import 'material_localizations.dart';
  22. import 'progress_indicator.dart';
  23. import 'theme.dart';
  24.  
  25. /// A material design data table that shows data using multiple pages.
  26. ///
  27. /// A paginated data table shows [rowsPerPage] rows of data per page and
  28. /// provides controls for showing other pages.
  29. ///
  30. /// Data is read lazily from from a [DataTableSource]. The widget is presented
  31. /// as a [Card].
  32. ///
  33. /// See also:
  34. ///
  35. /// * [DataTable], which is not paginated.
  36. /// * <https://material.io/go/design-data-tables#data-tables-tables-within-cards>
  37. class PaginatedDataTable extends StatefulWidget {
  38. /// Creates a widget describing a paginated [DataTable] on a [Card].
  39. ///
  40. /// The [header] should give the card's header, typically a [Text] widget. It
  41. /// must not be null.
  42. ///
  43. /// The [columns] argument must be a list of as many [DataColumn] objects as
  44. /// the table is to have columns, ignoring the leading checkbox column if any.
  45. /// The [columns] argument must have a length greater than zero and cannot be
  46. /// null.
  47. ///
  48. /// If the table is sorted, the column that provides the current primary key
  49. /// should be specified by index in [sortColumnIndex], 0 meaning the first
  50. /// column in [columns], 1 being the next one, and so forth.
  51. ///
  52. /// The actual sort order can be specified using [sortAscending]; if the sort
  53. /// order is ascending, this should be true (the default), otherwise it should
  54. /// be false.
  55. ///
  56. /// The [source] must not be null. The [source] should be a long-lived
  57. /// [DataTableSource]. The same source should be provided each time a
  58. /// particular [PaginatedDataTable] widget is created; avoid creating a new
  59. /// [DataTableSource] with each new instance of the [PaginatedDataTable]
  60. /// widget unless the data table really is to now show entirely different
  61. /// data from a new source.
  62. ///
  63. /// The [rowsPerPage] and [availableRowsPerPage] must not be null (they
  64. /// both have defaults, though, so don't have to be specified).
  65. PaginatedDataTable({
  66. Key key,
  67. this.header,
  68. this.actions,
  69. @required this.columns,
  70. this.sortColumnIndex,
  71. this.sortAscending = true,
  72. this.onSelectAll,
  73. this.dataRowHeight = kMinInteractiveDimension,
  74. this.headingRowHeight = 56.0,
  75. this.horizontalMargin = 24.0,
  76. this.columnSpacing = 56.0,
  77. this.showCheckboxColumn = true,
  78. this.initialFirstRowIndex = 0,
  79. this.onPageChanged,
  80. this.rowsPerPage = defaultRowsPerPage,
  81. this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
  82. this.onRowsPerPageChanged,
  83. this.dragStartBehavior = DragStartBehavior.start,
  84. @required this.source,
  85. }) :
  86. assert(columns != null),
  87. assert(dragStartBehavior != null),
  88. assert(columns.isNotEmpty),
  89. assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
  90. assert(sortAscending != null),
  91. assert(dataRowHeight != null),
  92. assert(headingRowHeight != null),
  93. assert(horizontalMargin != null),
  94. assert(columnSpacing != null),
  95. assert(showCheckboxColumn != null),
  96. assert(rowsPerPage != null),
  97. assert(rowsPerPage > 0),
  98. assert(() {
  99. if (onRowsPerPageChanged != null)
  100. assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage));
  101. return true;
  102. }()),
  103. assert(source != null),
  104. super(key: key);
  105.  
  106. /// The table card's header.
  107. ///
  108. /// This is typically a [Text] widget, but can also be a [ButtonBar] with
  109. /// [FlatButton]s. Suitable defaults are automatically provided for the font,
  110. /// button color, button padding, and so forth.
  111. ///
  112. /// If items in the table are selectable, then, when the selection is not
  113. /// empty, the header is replaced by a count of the selected items.
  114. final Widget header;
  115.  
  116. /// Icon buttons to show at the top right of the table.
  117. ///
  118. /// Typically, the exact actions included in this list will vary based on
  119. /// whether any rows are selected or not.
  120. ///
  121. /// These should be size 24.0 with default padding (8.0).
  122. final List<Widget> actions;
  123.  
  124. /// The configuration and labels for the columns in the table.
  125. final List<DataColumn> columns;
  126.  
  127. /// The current primary sort key's column.
  128. ///
  129. /// See [DataTable.sortColumnIndex].
  130. final int sortColumnIndex;
  131.  
  132. /// Whether the column mentioned in [sortColumnIndex], if any, is sorted
  133. /// in ascending order.
  134. ///
  135. /// See [DataTable.sortAscending].
  136. final bool sortAscending;
  137.  
  138. /// Invoked when the user selects or unselects every row, using the
  139. /// checkbox in the heading row.
  140. ///
  141. /// See [DataTable.onSelectAll].
  142. final ValueSetter<bool> onSelectAll;
  143.  
  144. /// The height of each row (excluding the row that contains column headings).
  145. ///
  146. /// This value is optional and defaults to kMinInteractiveDimension if not
  147. /// specified.
  148. final double dataRowHeight;
  149.  
  150. /// The height of the heading row.
  151. ///
  152. /// This value is optional and defaults to 56.0 if not specified.
  153. final double headingRowHeight;
  154.  
  155. /// The horizontal margin between the edges of the table and the content
  156. /// in the first and last cells of each row.
  157. ///
  158. /// When a checkbox is displayed, it is also the margin between the checkbox
  159. /// the content in the first data column.
  160. ///
  161. /// This value defaults to 24.0 to adhere to the Material Design specifications.
  162. final double horizontalMargin;
  163.  
  164. /// The horizontal margin between the contents of each data column.
  165. ///
  166. /// This value defaults to 56.0 to adhere to the Material Design specifications.
  167. final double columnSpacing;
  168.  
  169. /// {@macro flutter.material.dataTable.showCheckboxColumn}
  170. final bool showCheckboxColumn;
  171.  
  172. /// The index of the first row to display when the widget is first created.
  173. final int initialFirstRowIndex;
  174.  
  175. /// Invoked when the user switches to another page.
  176. ///
  177. /// The value is the index of the first row on the currently displayed page.
  178. final ValueChanged<int> onPageChanged;
  179.  
  180. /// The number of rows to show on each page.
  181. ///
  182. /// See also:
  183. ///
  184. /// * [onRowsPerPageChanged]
  185. /// * [defaultRowsPerPage]
  186. final int rowsPerPage;
  187.  
  188. /// The default value for [rowsPerPage].
  189. ///
  190. /// Useful when initializing the field that will hold the current
  191. /// [rowsPerPage], when implemented [onRowsPerPageChanged].
  192. static const int defaultRowsPerPage = 10;
  193.  
  194. /// The options to offer for the rowsPerPage.
  195. ///
  196. /// The current [rowsPerPage] must be a value in this list.
  197. ///
  198. /// The values in this list should be sorted in ascending order.
  199. final List<int> availableRowsPerPage;
  200.  
  201. /// Invoked when the user selects a different number of rows per page.
  202. ///
  203. /// If this is null, then the value given by [rowsPerPage] will be used
  204. /// and no affordance will be provided to change the value.
  205. final ValueChanged<int> onRowsPerPageChanged;
  206.  
  207. /// The data source which provides data to show in each row. Must be non-null.
  208. ///
  209. /// This object should generally have a lifetime longer than the
  210. /// [PaginatedDataTable] widget itself; it should be reused each time the
  211. /// [PaginatedDataTable] constructor is called.
  212. final DataTableSource source;
  213.  
  214. /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  215. final DragStartBehavior dragStartBehavior;
  216.  
  217. @override
  218. PaginatedDataTableState createState() => PaginatedDataTableState();
  219. }
  220.  
  221. /// Holds the state of a [PaginatedDataTable].
  222. ///
  223. /// The table can be programmatically paged using the [pageTo] method.
  224. class PaginatedDataTableState extends State<PaginatedDataTable> {
  225. int _firstRowIndex;
  226. int _rowCount;
  227. bool _rowCountApproximate;
  228. int _selectedRowCount;
  229. final Map<int, DataRow> _rows = <int, DataRow>{};
  230.  
  231. @override
  232. void initState() {
  233. super.initState();
  234. _firstRowIndex = PageStorage.of(context)?.readState(context) as int ?? widget.initialFirstRowIndex ?? 0;
  235. widget.source.addListener(_handleDataSourceChanged);
  236. _handleDataSourceChanged();
  237. }
  238.  
  239. @override
  240. void didUpdateWidget(PaginatedDataTable oldWidget) {
  241. super.didUpdateWidget(oldWidget);
  242. if (oldWidget.source != widget.source) {
  243. oldWidget.source.removeListener(_handleDataSourceChanged);
  244. widget.source.addListener(_handleDataSourceChanged);
  245. _handleDataSourceChanged();
  246. }
  247. }
  248.  
  249. @override
  250. void dispose() {
  251. widget.source.removeListener(_handleDataSourceChanged);
  252. super.dispose();
  253. }
  254.  
  255. void _handleDataSourceChanged() {
  256. setState(() {
  257. _rowCount = widget.source.rowCount;
  258. _rowCountApproximate = widget.source.isRowCountApproximate;
  259. _selectedRowCount = widget.source.selectedRowCount;
  260. _rows.clear();
  261. });
  262. }
  263.  
  264. /// Ensures that the given row is visible.
  265. void pageTo(int rowIndex) {
  266. final int oldFirstRowIndex = _firstRowIndex;
  267. setState(() {
  268. final int rowsPerPage = widget.rowsPerPage;
  269. _firstRowIndex = (rowIndex ~/ rowsPerPage) * rowsPerPage;
  270. });
  271. if ((widget.onPageChanged != null) &&
  272. (oldFirstRowIndex != _firstRowIndex))
  273. widget.onPageChanged(_firstRowIndex);
  274. }
  275.  
  276. DataRow _getBlankRowFor(int index) {
  277. return DataRow.byIndex(
  278. index: index,
  279. cells: widget.columns.map<DataCell>((DataColumn column) => DataCell.empty).toList(),
  280. );
  281. }
  282.  
  283. DataRow _getProgressIndicatorRowFor(int index) {
  284. bool haveProgressIndicator = false;
  285. final List<DataCell> cells = widget.columns.map<DataCell>((DataColumn column) {
  286. if (!column.numeric) {
  287. haveProgressIndicator = true;
  288. return const DataCell(CircularProgressIndicator());
  289. }
  290. return DataCell.empty;
  291. }).toList();
  292. if (!haveProgressIndicator) {
  293. haveProgressIndicator = true;
  294. cells[0] = const DataCell(CircularProgressIndicator());
  295. }
  296. return DataRow.byIndex(
  297. index: index,
  298. cells: cells,
  299. );
  300. }
  301.  
  302. List<DataRow> _getRows(int firstRowIndex, int rowsPerPage) {
  303. final List<DataRow> result = <DataRow>[];
  304. final int nextPageFirstRowIndex = firstRowIndex + rowsPerPage;
  305. bool haveProgressIndicator = false;
  306. for (int index = firstRowIndex; index < nextPageFirstRowIndex; index += 1) {
  307. DataRow row;
  308. if (index < _rowCount || _rowCountApproximate) {
  309. row = _rows.putIfAbsent(index, () => widget.source.getRow(index));
  310. if (row == null && !haveProgressIndicator) {
  311. row ??= _getProgressIndicatorRowFor(index);
  312. haveProgressIndicator = true;
  313. }
  314. }
  315. row ??= _getBlankRowFor(index);
  316. result.add(row);
  317. }
  318. return result;
  319. }
  320.  
  321. void _handlePrevious() {
  322. pageTo(math.max(_firstRowIndex - widget.rowsPerPage, 0));
  323. }
  324.  
  325. void _handleNext() {
  326. pageTo(_firstRowIndex + widget.rowsPerPage);
  327. }
  328.  
  329. final GlobalKey _tableKey = GlobalKey();
  330.  
  331. @override
  332. Widget build(BuildContext context) {
  333. // TODO(ianh): This whole build function doesn't handle RTL yet.
  334. assert(debugCheckHasMaterialLocalizations(context));
  335. final ThemeData themeData = Theme.of(context);
  336. final MaterialLocalizations localizations = MaterialLocalizations.of(context);
  337. // HEADER
  338. final List<Widget> headerWidgets = <Widget>[];
  339. double startPadding = 24.0;
  340. // headerWidgets.add(Text(""));
  341. // if (_selectedRowCount == 0) {
  342. // headerWidgets.add(Expanded(child: widget.header));
  343. // if (widget.header is ButtonBar) {
  344. // // We adjust the padding when a button bar is present, because the
  345. // // ButtonBar introduces 2 pixels of outside padding, plus 2 pixels
  346. // // around each button on each side, and the button itself will have 8
  347. // // pixels internally on each side, yet we want the left edge of the
  348. // // inside of the button to line up with the 24.0 left inset.
  349. // // TODO(ianh): Better magic. See https://github.com/flutter/flutter/issues/4460
  350. // startPadding = 12.0;
  351. // }
  352. // } else {
  353. // headerWidgets.add(Expanded(
  354. // child: Text(localizations.selectedRowCountTitle(_selectedRowCount)),
  355. // ));
  356. // }
  357. if (widget.actions != null) {
  358. headerWidgets.addAll(
  359. widget.actions.map<Widget>((Widget action) {
  360. return Padding(
  361. // 8.0 is the default padding of an icon button
  362. padding: const EdgeInsetsDirectional.only(start: 24.0 - 8.0 * 2.0),
  363. child: action,
  364. );
  365. }).toList()
  366. );
  367. }
  368.  
  369. // FOOTER
  370. final TextStyle footerTextStyle = themeData.textTheme.caption;
  371. final List<Widget> footerWidgets = <Widget>[];
  372. if (widget.onRowsPerPageChanged != null) {
  373. final List<Widget> availableRowsPerPage = widget.availableRowsPerPage
  374. .where((int value) => value <= _rowCount || value == widget.rowsPerPage)
  375. .map<DropdownMenuItem<int>>((int value) {
  376. return DropdownMenuItem<int>(
  377. value: value,
  378. child: Text('$value'),
  379. );
  380. })
  381. .toList();
  382. footerWidgets.addAll(<Widget>[
  383. Container(width: 14.0), // to match trailing padding in case we overflow and end up scrolling
  384. Text(localizations.rowsPerPageTitle),
  385. ConstrainedBox(
  386. constraints: const BoxConstraints(minWidth: 64.0), // 40.0 for the text, 24.0 for the icon
  387. child: Align(
  388. alignment: AlignmentDirectional.centerEnd,
  389. child: DropdownButtonHideUnderline(
  390. child: DropdownButton<int>(
  391. items: availableRowsPerPage.cast<DropdownMenuItem<int>>(),
  392. value: widget.rowsPerPage,
  393. onChanged: widget.onRowsPerPageChanged,
  394. style: footerTextStyle,
  395. iconSize: 24.0,
  396. ),
  397. ),
  398. ),
  399. ),
  400. ]);
  401. }
  402. footerWidgets.addAll(<Widget>[
  403. Container(width: 32.0),
  404. Text(
  405. localizations.pageRowsInfoTitle(
  406. _firstRowIndex + 1,
  407. _firstRowIndex + widget.rowsPerPage,
  408. _rowCount,
  409. _rowCountApproximate,
  410. ),
  411. ),
  412. Container(width: 32.0),
  413. IconButton(
  414. icon: const Icon(Icons.chevron_left),
  415. padding: EdgeInsets.zero,
  416. tooltip: localizations.previousPageTooltip,
  417. onPressed: _firstRowIndex <= 0 ? null : _handlePrevious,
  418. ),
  419. Container(width: 24.0),
  420. IconButton(
  421. icon: const Icon(Icons.chevron_right),
  422. padding: EdgeInsets.zero,
  423. tooltip: localizations.nextPageTooltip,
  424. onPressed: (!_rowCountApproximate && (_firstRowIndex + widget.rowsPerPage >= _rowCount)) ? null : _handleNext,
  425. ),
  426. Container(width: 14.0),
  427. ]);
  428.  
  429. // CARD
  430. return LayoutBuilder(
  431. builder: (BuildContext context, BoxConstraints constraints) {
  432. return Card(
  433. semanticContainer: false,
  434. child: Column(
  435. crossAxisAlignment: CrossAxisAlignment.stretch,
  436. children: <Widget>[
  437. // Semantics(
  438. // container: true,
  439. // child: DefaultTextStyle(
  440. // // These typographic styles aren't quite the regular ones. We pick the closest ones from the regular
  441. // // list and then tweak them appropriately.
  442. // // See https://material.io/design/components/data-tables.html#tables-within-cards
  443. // style: _selectedRowCount > 0 ? themeData.textTheme.subtitle1.copyWith(color: themeData.accentColor)
  444. // : themeData.textTheme.headline6.copyWith(fontWeight: FontWeight.w400),
  445. // child: IconTheme.merge(
  446. // data: const IconThemeData(
  447. // opacity: 0.54
  448. // ),
  449. // child: Ink(
  450. // height: 64.0,
  451. // color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
  452. // child: Padding(
  453. // padding: EdgeInsetsDirectional.only(start: startPadding, end: 14.0),
  454. // child: Row(
  455. // mainAxisAlignment: MainAxisAlignment.end,
  456. // children: headerWidgets,
  457. // ),
  458. // ),
  459. // ),
  460. // ),
  461. // ),
  462. // ),
  463. SingleChildScrollView(
  464. scrollDirection: Axis.horizontal,
  465. dragStartBehavior: widget.dragStartBehavior,
  466. child: ConstrainedBox(
  467. constraints: BoxConstraints(minWidth: constraints.minWidth),
  468. child: DataTable(
  469. key: _tableKey,
  470. columns: widget.columns,
  471. sortColumnIndex: widget.sortColumnIndex,
  472. sortAscending: widget.sortAscending,
  473. onSelectAll: widget.onSelectAll,
  474. dataRowHeight: widget.dataRowHeight,
  475. headingRowHeight: widget.headingRowHeight,
  476. horizontalMargin: widget.horizontalMargin,
  477. columnSpacing: widget.columnSpacing,
  478. rows: _getRows(_firstRowIndex, widget.rowsPerPage),
  479. ),
  480. ),
  481. ),
  482. DefaultTextStyle(
  483. style: footerTextStyle,
  484. child: IconTheme.merge(
  485. data: const IconThemeData(
  486. opacity: 0.54
  487. ),
  488. child: Container(
  489. // TODO(bkonyi): this won't handle text zoom correctly, https://github.com/flutter/flutter/issues/48522
  490. height: 56.0,
  491. child: SingleChildScrollView(
  492. dragStartBehavior: widget.dragStartBehavior,
  493. scrollDirection: Axis.horizontal,
  494. reverse: true,
  495. child: Row(
  496. children: footerWidgets,
  497. ),
  498. ),
  499. ),
  500. ),
  501. ),
  502. ],
  503. ),
  504. );
  505. },
  506. );
  507. }
  508. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement