美文网首页Flutter
flutter-固定标题的可滚动表格

flutter-固定标题的可滚动表格

作者: 浮华_du | 来源:发表于2021-08-18 16:23 被阅读0次

    如图,想要实现这种表格样式,如何实现呢?直接上代码喽...


    2441629273978_.pic.jpg

    1.使用DataTable

    
     int rowsCount = 100;
      int columnsCount = 30;
    
     return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: SingleChildScrollView(
            scrollDirection: Axis.vertical,
            child: DataTable(
              columns: columns(),
              rows: rows(),
            ),
          ),
        );
    
     List<DataColumn> columns() {
        List<DataColumn> column = [];
        for (var i = 0; i < columnsCount; i++) {
          column.add(DataColumn(label: Text('列标题$i')));
        }
        return column;
      }
    
      List<DataRow> rows() {
        //行
        List<DataRow> rows = [];
        for (var i = 0; i < rowsCount; i++) {
          rows.add(DataRow(cells: cells()));
        }
        return rows;
      }
    
      List<DataCell> cells() {
        List<DataCell> cells = [];
        for (var i = 0; i < columnsCount; i++) {
          cells.add(DataCell(Text('$i')));
        }
        return cells;
      }
    

    如上,即可实现一个表格,且可以上下左右滚动,但是顶部行 、左侧列无法固定, 这可怎么办呢? 我们可以在顶部、左侧加入列表,实现同步滚动不就可以了嘛

    参考 https://stackoom.com/question/3x2Bv
    此文章给出例子是在顶部、左侧加入了一个DataTable,但是同步滚动仅实现了滚动内部表格,左侧及顶部列表的同步滚动,并没有实现滚动左侧及顶部列表,表格跟随滚动的效果;

    1.
    • 于是,我们结合linked_scroll_controller库,修改成这个样子
    
    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>> {
      ScrollController? _columnController;
      ScrollController? _rowController;
      ScrollController? _subTableYController;
      ScrollController? _subTableXController;
      LinkedScrollControllerGroup? _verticalControllers;
      LinkedScrollControllerGroup? _horizontalControllers;
    
      @override
      void initState() {
        super.initState();
        _verticalControllers = LinkedScrollControllerGroup();
        _horizontalControllers = LinkedScrollControllerGroup();
        _columnController = _verticalControllers?.addAndGet();
        _subTableYController = _verticalControllers?.addAndGet();
        _rowController = _horizontalControllers?.addAndGet();
        _subTableXController = _horizontalControllers?.addAndGet();
      }
    
      @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(),//顶部列表
                  ),
                ),
              ],
            ),
          ],
        );
      }
    
      Widget _buildChild(double width, T? data) {//单元格元素
        if (data == null) {
          return SizedBox(width: width, child: Text(''));
        }
        return SizedBox(
            width: width, child: widget.cellBuilder?.call(data) ?? Text('$data'));
      }
    
    //左侧列表 
      Widget _buildFixedCol() => widget.fixedColCells == null
          ? SizedBox.shrink()
          : Material(
              color: Colors.blueGrey[100],
              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.blueGrey[100],
              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.white,
          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.blueGrey[100],
                  child: DataTable(
                      horizontalMargin: widget.cellMargin,
                      columnSpacing: widget.cellSpacing,
                      headingRowHeight: widget.cellHeight,
                      dataRowHeight: widget.cellHeight,
                      columns: [
                        DataColumn(
                            label: _buildChild(
                                widget.fixedColWidth, widget.fixedCornerCell))
                      ],
                      rows: []),
                );
    }
    
    
    • 调用显示
    
      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"
      ];
    
    
     return CustomDataTable(
          rowsCells: _rowsCells,//表格内单元格数据
          fixedColCells: _fixedColCells,//左侧标题数据
          fixedRowCells: _fixedRowCells,//顶部标题数据
          cellBuilder: (data) {
            return Center(//单元格内容
                child: Text('$data', style: TextStyle(color: Colors.black)));
          },
        );
    
    2.
    • 由上可实现固定标题的可滚动表格,但是我们可以发现,左侧及顶部列表也是使用表格实现,似乎不是那么必要,可修改为ListView实现
    
    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>> {
      ScrollController? _columnController;
      ScrollController? _rowController;
      ScrollController? _subTableYController;
      ScrollController? _subTableXController;
      LinkedScrollControllerGroup? _verticalControllers;
      LinkedScrollControllerGroup? _horizontalControllers;
      @override
      void initState() {
        super.initState();
        _verticalControllers = LinkedScrollControllerGroup();
        _horizontalControllers = LinkedScrollControllerGroup();
        _columnController = _verticalControllers?.addAndGet();
        _subTableYController = _verticalControllers?.addAndGet();
        _rowController = _horizontalControllers?.addAndGet();
        _subTableXController = _horizontalControllers?.addAndGet();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Positioned(
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              child: Row(
                children: <Widget>[
                  Container(
                    width: widget.fixedColWidth,
                    child: ListView.builder(
                        controller: _columnController,
                        itemBuilder: (cobtext, index) {
                          if (index == 0) {
                            return Container(
                              height: widget.cellHeight,
                              child: _buildChild(widget.fixedColWidth,
                                  widget.fixedColCells?.first),
                            );
                          }
                          return Container(
                            color: Colors.blueGrey[100],
                            height: widget.cellHeight,
                            child: _buildChild(widget.fixedColWidth,
                                widget.fixedColCells?[index - 1]),
                          );
                        },
                        itemCount: (widget.fixedColCells?.length ?? 0) == 0
                            ? 0
                            : (widget.fixedColCells?.length ?? 0) + 1),
                  ),
                  Flexible(
                    child: SingleChildScrollView(
                      controller: _subTableXController,
                      scrollDirection: Axis.horizontal,
                      child: SingleChildScrollView(
                        controller: _subTableYController,
                        scrollDirection: Axis.vertical,
                        child: _buildSubTable(), //中间表格
                      ),
                    ),
                  ),
                ],
              ),
            ),
            Positioned(
              top: 0,
              left: 0,
              right: 0,
              height: widget.cellHeight,
              child: Row(
                children: <Widget>[
                  // _buildCornerCell(),
                  Container(
                    color: Colors.blueGrey[100],
                    width: widget.fixedColWidth,
                    height: widget.cellHeight,
                  ),
                  Flexible(
                      child: Container(
                    color: Colors.blueGrey[100],
                    child: ListView.builder(
                        padding: EdgeInsets.only(
                          right: widget.cellMargin,
                        ),
                        scrollDirection: Axis.horizontal,
                        controller: _rowController,
                        itemBuilder: (context, index) {
                          return Container(
                            width: widget.cellWidth,
                            margin: EdgeInsets.only(
                              left: widget.cellMargin,
                            ),
                            height: widget.cellHeight,
                            child: _buildChild(
                                widget.fixedColWidth, widget.fixedRowCells?[index]),
                          );
                        },
                        itemCount: widget.fixedRowCells?.length),
                  )),
                ],
              ),
            ),
          ],
        );
      }
    
      Widget _buildChild(double width, T? data) {
        if (data == null) {
          return SizedBox();
        }
        return SizedBox(
            width: width, child: widget.cellBuilder?.call(data) ?? Text('$data'));
      }
    
    //中间表格
      Widget _buildSubTable() => Material(
          color: Colors.white,
          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()));
    }
    

    2. ListView+linked_scroll_controller库

    • 不使用表格,完全使用ListView的嵌套及同步滚动完成

    linked_scroll_controller 库: 可实现列表的同步滚动

    附上完整代码

    
    class TimeTablePage extends StatefulWidget {
      const TimeTablePage({Key? key}) : super(key: key);
    
      @override
      _TimeTablePageState createState() => _TimeTablePageState();
    }
    
    class _TimeTablePageState extends State<TimeTablePage> {
      int rowsCount = 100;
      int columnsCount = 30;
      double cellWidth = 80;
      double cellHeight = 50;
      ScrollController? leftController;
      ScrollController? topController;
      ScrollController? rowController;
      List<ScrollController> columnsController = [];
      LinkedScrollControllerGroup? _verticalControllers;
      LinkedScrollControllerGroup? _horizontalControllers;
    
      @override
      void initState() {
        super.initState();
        _verticalControllers = LinkedScrollControllerGroup();
        _horizontalControllers = LinkedScrollControllerGroup();
        leftController = _verticalControllers?.addAndGet();
        topController = _horizontalControllers?.addAndGet();
        rowController = _horizontalControllers?.addAndGet();
        for (var i = 0; i < columnsCount; i++) {
          ScrollController? columnController = _verticalControllers?.addAndGet();
          if (columnController != null) {
            columnsController.add(columnController);
          }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("工时表"),
          ),
          body: _bodyWidget(),
        );
      }
    
      Widget _bodyWidget() {
        return Row(children: [
          Container(
            width: cellWidth,
            child: Column(children: [
              Container(
                  decoration: BoxDecoration(
                      color: Colors.blueGrey[100],
                      border: Border.all(
                          color: ColorUtils.parseColorString("eeeeee"), width: 1)),
                  padding: EdgeInsets.only(left: 5.0, right: 5.0),
                  height: cellHeight,
                  width: cellWidth,
                  child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Container(
                          alignment: Alignment.topRight,
                          child: Text("时间"),
                        ),
                        Container(
                            alignment: Alignment.bottomLeft, child: Text("姓名"))
                      ])),
              Expanded(
                child: ListView.builder(
                  controller: leftController,
                  scrollDirection: Axis.vertical,
                  padding: EdgeInsets.all(0),
                  itemBuilder: (context, index) {
                    return Container(
                        decoration: BoxDecoration(
                            color: Colors.blueGrey[100],
                            border: Border.all(
                                color: ColorUtils.parseColorString("eeeeee"),
                                width: 1)),
                        height: cellHeight,
                        child: Center(child: Text("张${index + 1}")));
                  },
                  itemCount: rowsCount - 1,
                ),
              ),
            ]),
          ),
          Expanded(
              child: Column(children: [
            Container(
              height: cellHeight,
              child: ListView.builder(
                controller: topController,
                padding: EdgeInsets.all(0),
                scrollDirection: Axis.horizontal,
                itemBuilder: (context, index) {
                  return Container(
                      decoration: BoxDecoration(
                          color: Colors.blueGrey[100],
                          border: Border.all(
                              color: ColorUtils.parseColorString("eeeeee"),
                              width: 1)),
                      width: cellWidth,
                      child: Center(child: Text("8月${index + 1}日")));
                },
                itemCount: columnsCount,
              ),
            ),
            Expanded(
                child: ListView.builder(
              controller: rowController,
              padding: EdgeInsets.all(0),
              scrollDirection: Axis.horizontal,
              itemBuilder: (context, index) {
                return Container(
                  width: cellWidth,
                  child: ListView.builder(
                    controller: columnsController[index],
                    scrollDirection: Axis.vertical,
                    shrinkWrap: true,
                    padding: EdgeInsets.all(0),
                    itemBuilder: (context, innerindex) {
                      return Container(
                          height: cellHeight,
                          child: Center(child: Text("$innerindex, $index")));
                    },
                    itemCount: rowsCount - 1,
                  ),
                );
              },
              itemCount: columnsCount,
            ))
          ]))
        ]);
      }
    
      @override
      void dispose() {
        super.dispose();
        leftController?.dispose();
        topController?.dispose();
        rowController?.dispose();
        for (var controller in columnsController) {
          controller.dispose();
        }
      }
    }
    
    

    相关文章

      网友评论

        本文标题:flutter-固定标题的可滚动表格

        本文链接:https://www.haomeiwen.com/subject/fiigbltx.html