Flutter 132: 圖解 PaginatedDataTable 分頁表格

    小菜在嘗試列表展示時,對於固定類型數據庫表展示需要支持左右滑動,瞭解到 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 分頁數據表格也是通過 Columnheader 標題與 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 在右側添加 IconWidget,類似於 ToolBar;還可以通過 headingRowHeight 調整標題行的整體高度,默認是 56.0

header: Text('Flight Products'),
actions: [Icon(Icons.refresh), Icon(Icons.clear)],
headingRowHeight: 80.0,

3. dataRowHeight & horizontalMargin & columnSpacing

    dataRowHeight 爲數據元素行高,默認爲 48.0horizontalMargin 爲表格首列和尾列外邊距,默認爲 24.0columnSpacing 爲單元格間距,默認爲 56.0

dataRowHeight: 60.0,
horizontalMargin: 40.0,
columnSpacing: 80.0,

4. rowsPerPage & initialFirstRowIndex & onPageChanged

    rowsPerPage 爲每頁展示數據條數,默認爲 10onPageChanged 爲頁面左右切換時回調,回調結果爲數據索引值;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 案例源碼


    PaginatedDataTable 主要是通過 RenderObject 進行 applyPaintTransform 逐個計算單元格位置;小菜對其嘗試還不夠深入,如有錯誤,請多多指導!

來源: 阿策小和尚

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章