小菜在嘗試列表展示時,對於固定類型數據庫表展示需要支持左右滑動,瞭解到 PaginatedDataTable 分頁表格,學習一下設計思路;
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 分頁按鈕等封裝在一起的;
案例嘗試
1. header & columns & source
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()))
]);
}
2. actions & headingRowHeight
數據表的標題內容主要是通過 header 展示,而源碼標題是一個 Row 結構,可以通過 actions 在右側添加 Icon 等 Widget,類似於 ToolBar;還可以通過 headingRowHeight 調整標題行的整體高度,默認是 56.0;
header: Text('Flight Products'),
actions: [Icon(Icons.refresh), Icon(Icons.clear)],
headingRowHeight: 80.0,
3. dataRowHeight & horizontalMargin & columnSpacing
dataRowHeight 爲數據元素行高,默認爲 48.0;horizontalMargin 爲表格首列和尾列外邊距,默認爲 24.0;columnSpacing 爲單元格間距,默認爲 56.0;
dataRowHeight: 60.0,
horizontalMargin: 40.0,
columnSpacing: 80.0,
4. rowsPerPage & initialFirstRowIndex & onPageChanged
rowsPerPage 爲每頁展示數據條數,默認爲 10;onPageChanged 爲頁面左右切換時回調,回調結果爲數據索引值;initialFirstRowIndex 爲初始化展示索引位置,注意,若前置數據條數不滿足整數頁時,取整數頁前一頁;
rowsPerPage: 9,
initialFirstRowIndex: 20,
onPageChanged: (i) => print('onPageChanged -> $i'),
5. availableRowsPerPage & onRowsPerPageChanged
onRowsPerPageChanged 不爲空時可以設置左下角每頁展示行數;此時 availableRowsPerPage 列表不可爲空,且小菜測試,列表首個元素需要與初始化的行數一致;
var _rowsPerPage = 8;
rowsPerPage: _rowsPerPage,
availableRowsPerPage: [8, 16, 20],
onRowsPerPageChanged: (value) => setState(() => _rowsPerPage = value),
6. sortAscending & sortColumnIndex
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](https://upload-images.jianshu.io/upload_images/6187924-9fcfbafc05579b6b.gif?imageMogr2/auto-orient/strip)
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();
}
7. showCheckboxColumn & onSelectAll
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 主要是通過 RenderObject 進行 applyPaintTransform 逐個計算單元格位置;小菜對其嘗試還不夠深入,如有錯誤,請多多指導!
來源: 阿策小和尚