小菜在尝试列表展示时,对于固定类型数据库表展示需要支持左右滑动,了解到 PaginatedDataTable 分页表格,学习一下设计思路;
PaginatedDataTable({ Key key, @required 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, // 数据来源 })
简单分析源码可得,PaginatedDataTable 是由 DataTable 延伸而来的,并被 Card 包裹;区别在于 PaginatedDataTable 支持分页展示;
小菜将分页表单分为五部分,分别是 DataTable 整体数据表格、DataColumn 横向数据表头、DataRow 纵向数据列表、DataCell 数据表单元格以及 DataTableSource 数据来源;
而 PaginatedDataTable 分页数据表格也是通过 Column 将 header 标题与 DataTable 数据表格以及 footer 分页按钮等封装在一起的;
header & columns & source 作为基本 PaginatedDataTable 三个必要属性;其中 header 作为表格的标题,不可为空,建议常用的是 Text 也可以用 ButtonBar 按钮容器,日常其他 Widget 也是可以的;
columns 作为数据表头,是一个 DataColumn 列表,其中列表长度应与 source 资源列表数组长度一致,通过 label 来展示表头信息,也可以通过 onSort 回调来进行列表排序监听;
source 是来自 DataTableSource 类的数据源;主要实现四个抽象方法,分别是 getRow() 根据索引获取行内容、rowCount 数据源行数、isRowCountApproximate 行数是否确定以及 selectedRowCount 选中的行数(并非选中数组而是选中数量);
class _PaginatedPageState extends State<PaginatedDataTablePage> { DataTableSource _sourceData = SourceData(); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('PaginatedDataTable Page')), body: Column(children: [ SizedBox(height: 10), PaginatedDataTable( source: _sourceData, header: Text('Flight Products'), columns: [ DataColumn(label: Text('Avatar')), DataColumn(label: Text('ID')), DataColumn(label: Text('Name')), DataColumn(label: Row(children: [ Text('Price'), SizedBox(width: 5.0), Icon(Icons.airplanemode_active) ])), DataColumn(label: Text('No.')), DataColumn(label: Text('Address')) ]) ]))); } } class SourceData extends DataTableSource { final List<Map<String, dynamic>> _sourceData = List.generate( 200, (index) => { "avatar": (index % 3 == 1) ? 'images/icon_hzw01.jpg' : (index % 3 == 2) ? 'images/icon_hzw03.jpg' : 'images/icon_music.png', "id": (index + 1), "name": "Item Name ${(index + 1)}", "price": Random().nextInt(10000), "no.": Random().nextInt(10000), "address": (index % 3 == 1) ? 'Beijing' : (index % 3 == 2) ? 'New York' : 'Los Angeles' }); bool get isRowCountApproximate => false; int get rowCount => _sourceData.length; int get selectedRowCount => 0; DataRow getRow(int index) => DataRow(cells: [ DataCell(CircleAvatar(backgroundImage: AssetImage(_sourceData[index]["avatar"]))), DataCell(Text(_sourceData[index]['id'].toString())), DataCell(Text(_sourceData[index]['name'])), DataCell(Text('\$ ${_sourceData[index]['price']}')), DataCell(Text(_sourceData[index]['no.'].toString())), DataCell(Text(_sourceData[index]['address'].toString())) ]); }
数据表的标题内容主要是通过 header 展示,而源码标题是一个 Row 结构,可以通过 actions 在右侧添加 Icon 等 Widget,类似于 ToolBar;还可以通过 headingRowHeight 调整标题行的整体高度,默认是 56.0;
header: Text('Flight Products'), actions: [Icon(Icons.refresh), Icon(Icons.clear)], headingRowHeight: 80.0,
dataRowHeight 为数据元素行高,默认为 48.0;horizontalMargin 为表格首列和尾列外边距,默认为 24.0;columnSpacing 为单元格间距,默认为 56.0;
dataRowHeight: 60.0, horizontalMargin: 40.0, columnSpacing: 80.0,
rowsPerPage 为每页展示数据条数,默认为 10;onPageChanged 为页面左右切换时回调,回调结果为数据索引值;initialFirstRowIndex 为初始化展示索引位置,注意,若前置数据条数不满足整数页时,取整数页前一页;
rowsPerPage: 9, initialFirstRowIndex: 20, onPageChanged: (i) => print('onPageChanged -> $i'),
onRowsPerPageChanged 不为空时可以设置左下角每页展示行数;此时 availableRowsPerPage 列表不可为空,且小菜测试,列表首个元素需要与初始化的行数一致;
var _rowsPerPage = 8; rowsPerPage: _rowsPerPage, availableRowsPerPage: [8, 16, 20], onRowsPerPageChanged: (value) => setState(() => _rowsPerPage = value),
sortAscending 用于设置表格数据升序还是降序,需要配合 DataColumn 中的 onSort() 回调共同使用;sortColumnIndex 对应可升序降序的表头数组下标;
PaginatedDataTable( source: _sourceData, header: Text('Flight Products'), actions: [Icon(Icons.refresh), Icon(Icons.clear)], headingRowHeight: 50.0, dataRowHeight: 60.0, rowsPerPage: _rowsPerPage, onPageChanged: (i) => print('onPageChanged -> $i'), availableRowsPerPage: [8, 16, 20], onRowsPerPageChanged: (value) => setState(() => _rowsPerPage = value), sortAscending: _sortAscending, sortColumnIndex: 1, columns: [ DataColumn(label: Text('Avatar')), DataColumn( label: Text('ID'), onSort: (index, sortAscending) { setState(() { _sortAscending = sortAscending; _sourceData.sortData((map) => map['id'], sortAscending); }); }), DataColumn(label: Text('Name')), DataColumn(label: Row(children: [Text('Price'), SizedBox(width: 5.0), ![Table06.gif](http://img2.sycdn.imooc.com/6105dbaa0001e04400060005.jpg) Icon(Icons.airplanemode_active) ])), DataColumn(label: Text('No.')), DataColumn(label: Text('Address')) ]) void sortData<T>(Comparable<T> getField(Map<String, dynamic> map), bool b) { _sourceData.sort((Map<String, dynamic> map1, Map<String, dynamic> map2) { if (!b) { //两个项进行交换 final Map<String, dynamic> temp = map1; map1 = map2; map2 = temp; } final Comparable<T> s1Value = getField(map1); final Comparable<T> s2Value = getField(map2); return Comparable.compare(s1Value, s2Value); }); notifyListeners(); }
showCheckboxColumn 用于多选框显隐性,其前提是 DataTableSource 数据源中 DataRow 设置了 selected 属性;onSelectAll 为全选时回调,状态需要自己更新;
showCheckboxColumn: true, onSelectAll: (state) => setState(() => _sourceData.selectAll(state)), DataRow getRow(int index) => DataRow.byIndex( index: index, selected: _sourceData[index]["selected"], onSelectChanged: (selected) { _sourceData[index]["selected"] = selected; notifyListeners(); }, cells: [ DataCell(CircleAvatar(backgroundImage: AssetImage(_sourceData[index]["avatar"]))), DataCell(Text(_sourceData[index]['id'].toString())), DataCell(Text(_sourceData[index]['name'])), DataCell(Text('\$ ${_sourceData[index]['price']}')), DataCell(Text(_sourceData[index]['no.'].toString())), DataCell(Text(_sourceData[index]['address'].toString())) ]); void selectAll(bool checked) { _sourceData.forEach((data) => data["selected"] = checked); _selectCount = checked ? _sourceData.length : 0; notifyListeners(); //通知监听器去刷新 }
PaginatedDataTable 案例源码
PaginatedDataTable 主要是通过 RenderObject 进行 applyPaintTransform 逐个计算单元格位置;小菜对其尝试还不够深入,如有错误,请多多指导!
来源: 阿策小和尚