Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // 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 'dart:math' as math;
- import 'package:flutter/widgets.dart';
- import 'package:flutter/rendering.dart';
- import 'package:flutter/gestures.dart' show DragStartBehavior;
- import 'button_bar.dart';
- import 'card.dart';
- import 'constants.dart';
- import 'data_table.dart';
- import 'data_table_source.dart';
- import 'debug.dart';
- import 'dropdown.dart';
- import 'icon_button.dart';
- import 'icons.dart';
- import 'ink_decoration.dart';
- import 'material_localizations.dart';
- import 'progress_indicator.dart';
- import 'theme.dart';
- /// A material design data table that shows data using multiple pages.
- ///
- /// A paginated data table shows [rowsPerPage] rows of data per page and
- /// provides controls for showing other pages.
- ///
- /// Data is read lazily from from a [DataTableSource]. The widget is presented
- /// as a [Card].
- ///
- /// See also:
- ///
- /// * [DataTable], which is not paginated.
- /// * <https://material.io/go/design-data-tables#data-tables-tables-within-cards>
- class PaginatedDataTable extends StatefulWidget {
- /// Creates a widget describing a paginated [DataTable] on a [Card].
- ///
- /// The [header] should give the card's header, typically a [Text] widget. It
- /// must not be null.
- ///
- /// The [columns] argument must be a list of as many [DataColumn] objects as
- /// the table is to have columns, ignoring the leading checkbox column if any.
- /// The [columns] argument must have a length greater than zero and cannot be
- /// null.
- ///
- /// If the table is sorted, the column that provides the current primary key
- /// should be specified by index in [sortColumnIndex], 0 meaning the first
- /// column in [columns], 1 being the next one, and so forth.
- ///
- /// The actual sort order can be specified using [sortAscending]; if the sort
- /// order is ascending, this should be true (the default), otherwise it should
- /// be false.
- ///
- /// The [source] must not be null. The [source] should be a long-lived
- /// [DataTableSource]. The same source should be provided each time a
- /// particular [PaginatedDataTable] widget is created; avoid creating a new
- /// [DataTableSource] with each new instance of the [PaginatedDataTable]
- /// widget unless the data table really is to now show entirely different
- /// data from a new source.
- ///
- /// The [rowsPerPage] and [availableRowsPerPage] must not be null (they
- /// both have defaults, though, so don't have to be specified).
- PaginatedDataTable({
- Key key,
- this.header,
- this.actions,
- @required this.columns,
- this.sortColumnIndex,
- this.sortAscending = true,
- this.onSelectAll,
- this.dataRowHeight = kMinInteractiveDimension,
- this.headingRowHeight = 56.0,
- this.horizontalMargin = 24.0,
- this.columnSpacing = 56.0,
- this.showCheckboxColumn = true,
- this.initialFirstRowIndex = 0,
- this.onPageChanged,
- this.rowsPerPage = defaultRowsPerPage,
- this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
- this.onRowsPerPageChanged,
- this.dragStartBehavior = DragStartBehavior.start,
- @required this.source,
- }) :
- assert(columns != null),
- assert(dragStartBehavior != null),
- assert(columns.isNotEmpty),
- assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
- assert(sortAscending != null),
- assert(dataRowHeight != null),
- assert(headingRowHeight != null),
- assert(horizontalMargin != null),
- assert(columnSpacing != null),
- assert(showCheckboxColumn != null),
- assert(rowsPerPage != null),
- assert(rowsPerPage > 0),
- assert(() {
- if (onRowsPerPageChanged != null)
- assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage));
- return true;
- }()),
- assert(source != null),
- super(key: key);
- /// The table card's header.
- ///
- /// This is typically a [Text] widget, but can also be a [ButtonBar] with
- /// [FlatButton]s. Suitable defaults are automatically provided for the font,
- /// button color, button padding, and so forth.
- ///
- /// If items in the table are selectable, then, when the selection is not
- /// empty, the header is replaced by a count of the selected items.
- final Widget header;
- /// Icon buttons to show at the top right of the table.
- ///
- /// Typically, the exact actions included in this list will vary based on
- /// whether any rows are selected or not.
- ///
- /// These should be size 24.0 with default padding (8.0).
- final List<Widget> actions;
- /// The configuration and labels for the columns in the table.
- final List<DataColumn> columns;
- /// The current primary sort key's column.
- ///
- /// See [DataTable.sortColumnIndex].
- final int sortColumnIndex;
- /// Whether the column mentioned in [sortColumnIndex], if any, is sorted
- /// in ascending order.
- ///
- /// See [DataTable.sortAscending].
- final bool sortAscending;
- /// Invoked when the user selects or unselects every row, using the
- /// checkbox in the heading row.
- ///
- /// See [DataTable.onSelectAll].
- final ValueSetter<bool> onSelectAll;
- /// The height of each row (excluding the row that contains column headings).
- ///
- /// This value is optional and defaults to kMinInteractiveDimension if not
- /// specified.
- final double dataRowHeight;
- /// The height of the heading row.
- ///
- /// This value is optional and defaults to 56.0 if not specified.
- final double headingRowHeight;
- /// The horizontal margin between the edges of the table and the content
- /// in the first and last cells of each row.
- ///
- /// When a checkbox is displayed, it is also the margin between the checkbox
- /// the content in the first data column.
- ///
- /// This value defaults to 24.0 to adhere to the Material Design specifications.
- final double horizontalMargin;
- /// The horizontal margin between the contents of each data column.
- ///
- /// This value defaults to 56.0 to adhere to the Material Design specifications.
- final double columnSpacing;
- /// {@macro flutter.material.dataTable.showCheckboxColumn}
- final bool showCheckboxColumn;
- /// The index of the first row to display when the widget is first created.
- final int initialFirstRowIndex;
- /// Invoked when the user switches to another page.
- ///
- /// The value is the index of the first row on the currently displayed page.
- final ValueChanged<int> onPageChanged;
- /// The number of rows to show on each page.
- ///
- /// See also:
- ///
- /// * [onRowsPerPageChanged]
- /// * [defaultRowsPerPage]
- final int rowsPerPage;
- /// The default value for [rowsPerPage].
- ///
- /// Useful when initializing the field that will hold the current
- /// [rowsPerPage], when implemented [onRowsPerPageChanged].
- static const int defaultRowsPerPage = 10;
- /// The options to offer for the rowsPerPage.
- ///
- /// The current [rowsPerPage] must be a value in this list.
- ///
- /// The values in this list should be sorted in ascending order.
- final List<int> availableRowsPerPage;
- /// Invoked when the user selects a different number of rows per page.
- ///
- /// If this is null, then the value given by [rowsPerPage] will be used
- /// and no affordance will be provided to change the value.
- final ValueChanged<int> onRowsPerPageChanged;
- /// The data source which provides data to show in each row. Must be non-null.
- ///
- /// This object should generally have a lifetime longer than the
- /// [PaginatedDataTable] widget itself; it should be reused each time the
- /// [PaginatedDataTable] constructor is called.
- final DataTableSource source;
- /// {@macro flutter.widgets.scrollable.dragStartBehavior}
- final DragStartBehavior dragStartBehavior;
- @override
- PaginatedDataTableState createState() => PaginatedDataTableState();
- }
- /// Holds the state of a [PaginatedDataTable].
- ///
- /// The table can be programmatically paged using the [pageTo] method.
- class PaginatedDataTableState extends State<PaginatedDataTable> {
- int _firstRowIndex;
- int _rowCount;
- bool _rowCountApproximate;
- int _selectedRowCount;
- final Map<int, DataRow> _rows = <int, DataRow>{};
- @override
- void initState() {
- super.initState();
- _firstRowIndex = PageStorage.of(context)?.readState(context) as int ?? widget.initialFirstRowIndex ?? 0;
- widget.source.addListener(_handleDataSourceChanged);
- _handleDataSourceChanged();
- }
- @override
- void didUpdateWidget(PaginatedDataTable oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (oldWidget.source != widget.source) {
- oldWidget.source.removeListener(_handleDataSourceChanged);
- widget.source.addListener(_handleDataSourceChanged);
- _handleDataSourceChanged();
- }
- }
- @override
- void dispose() {
- widget.source.removeListener(_handleDataSourceChanged);
- super.dispose();
- }
- void _handleDataSourceChanged() {
- setState(() {
- _rowCount = widget.source.rowCount;
- _rowCountApproximate = widget.source.isRowCountApproximate;
- _selectedRowCount = widget.source.selectedRowCount;
- _rows.clear();
- });
- }
- /// Ensures that the given row is visible.
- void pageTo(int rowIndex) {
- final int oldFirstRowIndex = _firstRowIndex;
- setState(() {
- final int rowsPerPage = widget.rowsPerPage;
- _firstRowIndex = (rowIndex ~/ rowsPerPage) * rowsPerPage;
- });
- if ((widget.onPageChanged != null) &&
- (oldFirstRowIndex != _firstRowIndex))
- widget.onPageChanged(_firstRowIndex);
- }
- DataRow _getBlankRowFor(int index) {
- return DataRow.byIndex(
- index: index,
- cells: widget.columns.map<DataCell>((DataColumn column) => DataCell.empty).toList(),
- );
- }
- DataRow _getProgressIndicatorRowFor(int index) {
- bool haveProgressIndicator = false;
- final List<DataCell> cells = widget.columns.map<DataCell>((DataColumn column) {
- if (!column.numeric) {
- haveProgressIndicator = true;
- return const DataCell(CircularProgressIndicator());
- }
- return DataCell.empty;
- }).toList();
- if (!haveProgressIndicator) {
- haveProgressIndicator = true;
- cells[0] = const DataCell(CircularProgressIndicator());
- }
- return DataRow.byIndex(
- index: index,
- cells: cells,
- );
- }
- List<DataRow> _getRows(int firstRowIndex, int rowsPerPage) {
- final List<DataRow> result = <DataRow>[];
- final int nextPageFirstRowIndex = firstRowIndex + rowsPerPage;
- bool haveProgressIndicator = false;
- for (int index = firstRowIndex; index < nextPageFirstRowIndex; index += 1) {
- DataRow row;
- if (index < _rowCount || _rowCountApproximate) {
- row = _rows.putIfAbsent(index, () => widget.source.getRow(index));
- if (row == null && !haveProgressIndicator) {
- row ??= _getProgressIndicatorRowFor(index);
- haveProgressIndicator = true;
- }
- }
- row ??= _getBlankRowFor(index);
- result.add(row);
- }
- return result;
- }
- void _handlePrevious() {
- pageTo(math.max(_firstRowIndex - widget.rowsPerPage, 0));
- }
- void _handleNext() {
- pageTo(_firstRowIndex + widget.rowsPerPage);
- }
- final GlobalKey _tableKey = GlobalKey();
- @override
- Widget build(BuildContext context) {
- // TODO(ianh): This whole build function doesn't handle RTL yet.
- assert(debugCheckHasMaterialLocalizations(context));
- final ThemeData themeData = Theme.of(context);
- final MaterialLocalizations localizations = MaterialLocalizations.of(context);
- // HEADER
- final List<Widget> headerWidgets = <Widget>[];
- double startPadding = 24.0;
- // headerWidgets.add(Text(""));
- // if (_selectedRowCount == 0) {
- // headerWidgets.add(Expanded(child: widget.header));
- // if (widget.header is ButtonBar) {
- // // We adjust the padding when a button bar is present, because the
- // // ButtonBar introduces 2 pixels of outside padding, plus 2 pixels
- // // around each button on each side, and the button itself will have 8
- // // pixels internally on each side, yet we want the left edge of the
- // // inside of the button to line up with the 24.0 left inset.
- // // TODO(ianh): Better magic. See https://github.com/flutter/flutter/issues/4460
- // startPadding = 12.0;
- // }
- // } else {
- // headerWidgets.add(Expanded(
- // child: Text(localizations.selectedRowCountTitle(_selectedRowCount)),
- // ));
- // }
- if (widget.actions != null) {
- headerWidgets.addAll(
- widget.actions.map<Widget>((Widget action) {
- return Padding(
- // 8.0 is the default padding of an icon button
- padding: const EdgeInsetsDirectional.only(start: 24.0 - 8.0 * 2.0),
- child: action,
- );
- }).toList()
- );
- }
- // FOOTER
- final TextStyle footerTextStyle = themeData.textTheme.caption;
- final List<Widget> footerWidgets = <Widget>[];
- if (widget.onRowsPerPageChanged != null) {
- final List<Widget> availableRowsPerPage = widget.availableRowsPerPage
- .where((int value) => value <= _rowCount || value == widget.rowsPerPage)
- .map<DropdownMenuItem<int>>((int value) {
- return DropdownMenuItem<int>(
- value: value,
- child: Text('$value'),
- );
- })
- .toList();
- footerWidgets.addAll(<Widget>[
- Container(width: 14.0), // to match trailing padding in case we overflow and end up scrolling
- Text(localizations.rowsPerPageTitle),
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 64.0), // 40.0 for the text, 24.0 for the icon
- child: Align(
- alignment: AlignmentDirectional.centerEnd,
- child: DropdownButtonHideUnderline(
- child: DropdownButton<int>(
- items: availableRowsPerPage.cast<DropdownMenuItem<int>>(),
- value: widget.rowsPerPage,
- onChanged: widget.onRowsPerPageChanged,
- style: footerTextStyle,
- iconSize: 24.0,
- ),
- ),
- ),
- ),
- ]);
- }
- footerWidgets.addAll(<Widget>[
- Container(width: 32.0),
- Text(
- localizations.pageRowsInfoTitle(
- _firstRowIndex + 1,
- _firstRowIndex + widget.rowsPerPage,
- _rowCount,
- _rowCountApproximate,
- ),
- ),
- Container(width: 32.0),
- IconButton(
- icon: const Icon(Icons.chevron_left),
- padding: EdgeInsets.zero,
- tooltip: localizations.previousPageTooltip,
- onPressed: _firstRowIndex <= 0 ? null : _handlePrevious,
- ),
- Container(width: 24.0),
- IconButton(
- icon: const Icon(Icons.chevron_right),
- padding: EdgeInsets.zero,
- tooltip: localizations.nextPageTooltip,
- onPressed: (!_rowCountApproximate && (_firstRowIndex + widget.rowsPerPage >= _rowCount)) ? null : _handleNext,
- ),
- Container(width: 14.0),
- ]);
- // CARD
- return LayoutBuilder(
- builder: (BuildContext context, BoxConstraints constraints) {
- return Card(
- semanticContainer: false,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: <Widget>[
- // Semantics(
- // container: true,
- // child: DefaultTextStyle(
- // // These typographic styles aren't quite the regular ones. We pick the closest ones from the regular
- // // list and then tweak them appropriately.
- // // See https://material.io/design/components/data-tables.html#tables-within-cards
- // style: _selectedRowCount > 0 ? themeData.textTheme.subtitle1.copyWith(color: themeData.accentColor)
- // : themeData.textTheme.headline6.copyWith(fontWeight: FontWeight.w400),
- // child: IconTheme.merge(
- // data: const IconThemeData(
- // opacity: 0.54
- // ),
- // child: Ink(
- // height: 64.0,
- // color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
- // child: Padding(
- // padding: EdgeInsetsDirectional.only(start: startPadding, end: 14.0),
- // child: Row(
- // mainAxisAlignment: MainAxisAlignment.end,
- // children: headerWidgets,
- // ),
- // ),
- // ),
- // ),
- // ),
- // ),
- SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- dragStartBehavior: widget.dragStartBehavior,
- child: ConstrainedBox(
- constraints: BoxConstraints(minWidth: constraints.minWidth),
- child: DataTable(
- key: _tableKey,
- columns: widget.columns,
- sortColumnIndex: widget.sortColumnIndex,
- sortAscending: widget.sortAscending,
- onSelectAll: widget.onSelectAll,
- dataRowHeight: widget.dataRowHeight,
- headingRowHeight: widget.headingRowHeight,
- horizontalMargin: widget.horizontalMargin,
- columnSpacing: widget.columnSpacing,
- rows: _getRows(_firstRowIndex, widget.rowsPerPage),
- ),
- ),
- ),
- DefaultTextStyle(
- style: footerTextStyle,
- child: IconTheme.merge(
- data: const IconThemeData(
- opacity: 0.54
- ),
- child: Container(
- // TODO(bkonyi): this won't handle text zoom correctly, https://github.com/flutter/flutter/issues/48522
- height: 56.0,
- child: SingleChildScrollView(
- dragStartBehavior: widget.dragStartBehavior,
- scrollDirection: Axis.horizontal,
- reverse: true,
- child: Row(
- children: footerWidgets,
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- );
- },
- );
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement