【问题标题】:Fixed column and row header for DataTable on Flutter Dart修复了 Flutter Dart 上 DataTable 的列和行标题
【发布时间】:2020-02-12 09:03:42
【问题描述】:

我已经使用DataTable 在 Flutter Dart 上构建了一个表。这张表非常大,我同时使用垂直和水平滚动。 滚动时我失去了对列的引用,我需要知道该列是什么。

例如。在屏幕截图上,我不知道数字 20.025.0 的含义,除非我滚动到顶部。

我添加了一个我想要实现的 GIF 示例。 (使用 LibreOffice)。我需要固定的列名(第一行)。

表格示例,在表格中间滚动时:

我想做的例子:

我的表的代码示例:

    return SingleChildScrollView(
      scrollDirection: Axis.vertical,
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: DataTable(
          columns: MyDataSet.getColumns(),
          rows: widget._data.map<DataRow>((row) => DataRow(
            onSelectChanged: (d) {
              setState(() {
                selectedRow = d ? row.hashCode : null;
              });
            },
            selected: row.hashCode == selectedRow,
            cells: MyDataSet.toDataCells(row)
          )).toList()
        )
      ),
    );

缺少代码示例:

return columns.map<DataColumn>((name) => DataColumn(
      label: Text(name, style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black),)
    )).toList();

更新(2019 年 10 月 24 日)

如果标题名称与单元格内容的大小相同,则当前代码运行良好。否则两种尺寸都会不同。


更新 (21/02/2020)

人们为此创建了一个包。 :D

https://pub.dev/packages/table_sticky_headers

来自 pub.dev 的图片!

【问题讨论】:

  • 如果我理解正确,请告诉我:如果您垂直滚动整个第一行应该固定,如果您水平滚动整个第一列应该固定?
  • 没错。在上面的屏幕截图中,没有提及该列的含义(列标题)。而且也没有参考该行的内容(行第一列)。在 Excel 和 Google Calc 上,这称为 Freeze Header,视频示例:youtu.be/dTpwj74hTfE
  • @PabloBarrera 我添加了一个 GIF,显示了我想要实现的目标。我也改进了解释。
  • GIF 不显示水平滚动,是吗?
  • 示例不完整。我正在尝试做什么,如果我水平滚动,第一行将滚动在一起。始终显示列名(第一行)。

标签: flutter dart datatable


【解决方案1】:

我可以想出一个使用滚动控制器的解决方法,如下所示:Video

基本上它是第一行的水平滚动,第一列的垂直滚动和子表的混合水平和垂直滚动。然后,当您移动子表时,其控制器会移动列和行。

这是一个自定义小部件,其中包含如何使用它的示例:

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(),
              ),
            ),
          ],
        ),
      ],
    );
  }
}

由于第一列、第一行和子表是独立的,我不得不为每一个创建一个DataTable。并且由于DataTable有无法删除的表头,所以第一列和子表的表头都被第一行隐藏了。

另外,我必须使第一列和第一行不能手动滚动,因为如果滚动它们,子表将不会滚动。

这可能不是最好的解决方案,但目前似乎不是另一种方法。您可以尝试改进这种方法,也许使用Table 或其他小部件而不是DataTable,至少可以避免隐藏子表和第一列的标题。

【讨论】:

  • 我在使这段代码工作时遇到了一些麻烦。可以分享完整代码吗?
  • 我认为有一个属性可以在 DataTable 中设置大小,因为它们是独立的 DataTable,您可能需要同样设置它们的大小。当我可以检查时,我会编辑我的答案。
  • 我用 CustomDataTable 更新了我的答案,您可以在其中设置信息以填充表格和单元格的宽度。我认为您可以了解如何改进它。
  • 如果您使用的是 CustomDataTable 小部件,您只需指定 fixedCornerCell 属性。您可以检查 CustomDataTable 中的 _buildCornerCell() 方法。
  • @GustavoGarcia 你有 Github 链接吗?我遇到了很多错误。
【解决方案2】:

几个月前,我遇到了类似的问题,即库存 DataTable 和 PaginatedDataTable2 小部件的功能有限,这些小部件不允许修复标题。最终,我将这些小部件分开并创建了我自己的版本,但带有二十一点和固定的标题行。这是 pub.dev 上的插件: https://pub.dev/packages/data_table_2

DataTable2 和 PaginatedDataTable2 类提供与原始版本完全相同的 API。

注意:这些只实现粘性顶行,最左边的列不固定/粘性

【讨论】:

  • 太棒了。一直在寻找可以完美映射到原始数据表的固定标头解决方案。我一直在避免使用其他解决方案,因为它们带来了新的不直观的语法和问题,我正要破解数据表代码,但你已经完成了艰苦的工作。干杯
【解决方案3】:

试试flutter包horizontal_data_table
一个 Flutter Widget,它创建一个在左侧具有固定列的水平表。

dependencies:
  horizontal_data_table: ^2.5.0

【讨论】:

  • 这确实是 2021 年最好的解决方案。它解决了如果不使用固定列大小无法修复标题的问题。我会将我的应用转换为该表并进行测试。
猜你喜欢
  • 1970-01-01
  • 2020-02-17
  • 2023-02-17
  • 2021-08-08
  • 1970-01-01
  • 2020-06-20
  • 1970-01-01
  • 2018-06-19
  • 2019-08-26
相关资源
最近更新 更多