Fixed column and row header for DataTable on Flutter Dart

前端 未结 2 1952
南方客
南方客 2020-12-19 12:11

I\'ve build a table on Flutter Dart using DataTable. This table is very large, and I\'m using both Vertical and Horizontal scrolling. When scrolling I lose refe

相关标签:
2条回答
  • 2020-12-19 12:58

    Good afternoon, I got your code and I'm adapting for testing, I created a dynamic list for _fixedColCells

    worked perfectly,

    var disciplina =[];
         for (var c = 0; c <= notas?.length -1; c++) {
         disciplina.add(notas[c].disciplina.toString())  ;
        }
    

    ..... fixedColCells: disciplina, ....

    _fixedRowCells is a fixed String list, so I used the same variable just by hitting cell labels

    but _rowsCells, which by the statement is a <list <list <dynamic>>, set the routine is equal to the fixed variable of the example, but gives error.

    type List<dynamic> is not a subtype of type List<List<dynamic>>

    and print output basically equals fixed variable _rowsCells example, see below

    [[8.5, 7.0, 6.5, 0.0, 5.5, 5.5, 5.5, 0.0, 0.0, 5.5, ATIVA], 
    [8.5, 6.0, 8.5, 0.0, 6.0, 6.0, 6.0, 0.0, 0.0, 6.0, ATIVA], 
    [7.5, 7.5, 6.0, 0.0, 5.5, 5.5, 5.5, 0.0, 0.0, 5.5, ATIVA], 
    [9.5, 9.0, 8.0, 0.0, 6.5, 6.5, 6.5, 0.0, 0.0, 6.5, ATIVA], 
    [7.5, 6.0, 8.0, 0.0, 5.5, 5.5, 5.5, 0.0, 0.0, 5.5, ATIVA], 
    [10.0, 7.5, 8.5, 0.0, 6.5, 6.5, 6.5, 0.0, 0.0, 6.5, ATIVA], 
    [9.0, 9.5, 8.0, 0.0, 6.5, 6.5, 6.5, 0.0, 0.0, 6.5, ATIVA], 
    [10.0, 10.0, 10.0, 0.0, 7.5, 7.5, 7.5, 0.0, 0.0, 7.5, ATIVA], 
    [10.0, 9.5, 9.5, 0.0, 7.5, 7.5, 7.5, 0.0, 0.0, 7.5, ATIVA], 
    [9.0, 7.0, 8.5, 0.0, 6.0, 6.5, 6.5, 0.0, 0.0, 6.0, ATIVA], 
    [6.0, 6.0, 6.5, 0.0, 4.5, 4.5, 4.5, 0.0, 0.0, 4.5, ATIVA], 
    [9.0, 7.5, 8.5, 0.0, 6.5, 6.5, 6.5, 0.0, 0.0, 6.5, ATIVA], 
    [5.0, 5.0, 7.5, 0.0, 4.5, 4.5, 4.5, 0.0, 0.0, 4.5, ATIVA], 
    [8.5, 9.0, 8.0, 0.0, 6.5, 6.5, 6.5, 0.0, 0.0, 6.5, ATIVA]]
    

    Thanks for the possible help.

    0 讨论(0)
  • 2020-12-19 13:01

    I could come up with a workaround using scroll controllers, looks like this: Video

    Basically it's an horizontal scroll for the first row, a vertical scroll for the first column and a mixed horizontal and vertical scroll for the subtable. Then when you move the subtable, its controllers move the column and the row.

    Here is a custom widget with an example of how to use it:

    final _rowsCells = [
      [7, 8, 10, 8, 7],
      [10, 10, 9, 6, 6],
      [5, 4, 5, 7, 5],
      [9, 4, 1, 7, 8],
      [7, 8, 10, 8, 7],
      [10, 10, 9, 6, 6],
      [5, 4, 5, 7, 5],
      [9, 4, 1, 7, 8],
      [7, 8, 10, 8, 7],
      [10, 10, 9, 6, 6],
      [5, 4, 5, 7, 5],
      [9, 4, 1, 7, 8],
      [7, 8, 10, 8, 7],
      [10, 10, 9, 6, 6],
      [5, 4, 5, 7, 5],
      [9, 4, 1, 7, 8]
    ];
    final _fixedColCells = [
      "Pablo",
      "Gustavo",
      "John",
      "Jack",
      "Pablo",
      "Gustavo",
      "John",
      "Jack",
      "Pablo",
      "Gustavo",
      "John",
      "Jack",
      "Pablo",
      "Gustavo",
      "John",
      "Jack",
    ];
    final _fixedRowCells = [
      "Math",
      "Informatics",
      "Geography",
      "Physics",
      "Biology"
    ];
    
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(),
        body: CustomDataTable(
          rowsCells: _rowsCells,
          fixedColCells: _fixedColCells,
          fixedRowCells: _fixedRowCells,
          cellBuilder: (data) {
            return Text('$data', style: TextStyle(color: Colors.red));
          },
        ),
      );
    }
    
    class CustomDataTable<T> extends StatefulWidget {
      final T fixedCornerCell;
      final List<T> fixedColCells;
      final List<T> fixedRowCells;
      final List<List<T>> rowsCells;
      final Widget Function(T data) cellBuilder;
      final double fixedColWidth;
      final double cellWidth;
      final double cellHeight;
      final double cellMargin;
      final double cellSpacing;
    
      CustomDataTable({
        this.fixedCornerCell,
        this.fixedColCells,
        this.fixedRowCells,
        @required this.rowsCells,
        this.cellBuilder,
        this.fixedColWidth = 60.0,
        this.cellHeight = 56.0,
        this.cellWidth = 120.0,
        this.cellMargin = 10.0,
        this.cellSpacing = 10.0,
      });
    
      @override
      State<StatefulWidget> createState() => CustomDataTableState();
    }
    
    class CustomDataTableState<T> extends State<CustomDataTable<T>> {
      final _columnController = ScrollController();
      final _rowController = ScrollController();
      final _subTableYController = ScrollController();
      final _subTableXController = ScrollController();
    
      Widget _buildChild(double width, T data) => SizedBox(
          width: width, child: widget.cellBuilder?.call(data) ?? Text('$data'));
    
      Widget _buildFixedCol() => widget.fixedColCells == null
          ? SizedBox.shrink()
          : Material(
              color: Colors.lightBlueAccent,
              child: DataTable(
                  horizontalMargin: widget.cellMargin,
                  columnSpacing: widget.cellSpacing,
                  headingRowHeight: widget.cellHeight,
                  dataRowHeight: widget.cellHeight,
                  columns: [
                    DataColumn(
                        label: _buildChild(
                            widget.fixedColWidth, widget.fixedColCells.first))
                  ],
                  rows: widget.fixedColCells
                      .sublist(widget.fixedRowCells == null ? 1 : 0)
                      .map((c) => DataRow(
                          cells: [DataCell(_buildChild(widget.fixedColWidth, c))]))
                      .toList()),
            );
    
      Widget _buildFixedRow() => widget.fixedRowCells == null
          ? SizedBox.shrink()
          : Material(
              color: Colors.greenAccent,
              child: DataTable(
                  horizontalMargin: widget.cellMargin,
                  columnSpacing: widget.cellSpacing,
                  headingRowHeight: widget.cellHeight,
                  dataRowHeight: widget.cellHeight,
                  columns: widget.fixedRowCells
                      .map((c) =>
                          DataColumn(label: _buildChild(widget.cellWidth, c)))
                      .toList(),
                  rows: []),
            );
    
      Widget _buildSubTable() => Material(
          color: Colors.lightGreenAccent,
          child: DataTable(
              horizontalMargin: widget.cellMargin,
              columnSpacing: widget.cellSpacing,
              headingRowHeight: widget.cellHeight,
              dataRowHeight: widget.cellHeight,
              columns: widget.rowsCells.first
                  .map((c) => DataColumn(label: _buildChild(widget.cellWidth, c)))
                  .toList(),
              rows: widget.rowsCells
                  .sublist(widget.fixedRowCells == null ? 1 : 0)
                  .map((row) => DataRow(
                      cells: row
                          .map((c) => DataCell(_buildChild(widget.cellWidth, c)))
                          .toList()))
                  .toList()));
    
      Widget _buildCornerCell() =>
          widget.fixedColCells == null || widget.fixedRowCells == null
              ? SizedBox.shrink()
              : Material(
                  color: Colors.amberAccent,
                  child: DataTable(
                      horizontalMargin: widget.cellMargin,
                      columnSpacing: widget.cellSpacing,
                      headingRowHeight: widget.cellHeight,
                      dataRowHeight: widget.cellHeight,
                      columns: [
                        DataColumn(
                            label: _buildChild(
                                widget.fixedColWidth, widget.fixedCornerCell))
                      ],
                      rows: []),
                );
    
      @override
      void initState() {
        super.initState();
        _subTableXController.addListener(() {
          _rowController.jumpTo(_subTableXController.position.pixels);
        });
        _subTableYController.addListener(() {
          _columnController.jumpTo(_subTableYController.position.pixels);
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Row(
              children: <Widget>[
                SingleChildScrollView(
                  controller: _columnController,
                  scrollDirection: Axis.vertical,
                  physics: NeverScrollableScrollPhysics(),
                  child: _buildFixedCol(),
                ),
                Flexible(
                  child: SingleChildScrollView(
                    controller: _subTableXController,
                    scrollDirection: Axis.horizontal,
                    child: SingleChildScrollView(
                      controller: _subTableYController,
                      scrollDirection: Axis.vertical,
                      child: _buildSubTable(),
                    ),
                  ),
                ),
              ],
            ),
            Row(
              children: <Widget>[
                _buildCornerCell(),
                Flexible(
                  child: SingleChildScrollView(
                    controller: _rowController,
                    scrollDirection: Axis.horizontal,
                    physics: NeverScrollableScrollPhysics(),
                    child: _buildFixedRow(),
                  ),
                ),
              ],
            ),
          ],
        );
      }
    }
    

    Since the first column, the first row and the subtable are independent, I had to create a DataTable for each one. And since DataTable has headers that can't be removed, the headers of the first column and the subtable are hidden by the first row.

    Also, I had to make the first column and first row not manually scrollable because if you scroll them the subtable won't scroll.

    This might not be the best solution, but at the moment doesn't seem to be another way to do it. You could try to improve this approach, maybe using Table or other widgets instead of DataTable at least you could avoid hiding the headers of the subtable and first column.

    0 讨论(0)
提交回复
热议问题