当前位置: 首页 > article >正文

Flutter用700行代码纯手工自定义绘制表格控件KqTable

我们项目中往往需要使用到自定义表格,系统提供的表格控件只支持简单的展示功能,并不能很好的满足我们项目的自定义,然而自定义最大自由度的还是自己绘制,所以我选则了自己从头开始自定义绘制一个表格,绘制中不仅需要考虑到事件的处理,还需要考虑到表格固定行列的处理,绘制时采用了数据预处理策略,先对需要绘制的数据进行层级排序,然后根据层级排序,控制绘制顺序。而且项目中还用到了局部绘制的概念,即只绘制出当前正在展示的表格,根据表格的滑动,动态绘制需要展示的内容。感兴趣的伙伴可以直接复制代码使用和修改。

  • 演示

  • 功能

1.支持动态数据绘制。数据格式[[row],[row],...]。

2.支持表格中文字的大小与颜色设置。

3.支持控件宽高设置。

4.支持设置表格的格子背景颜色与边框颜色。

5.支持固定上下左右行列,固定遵循格式,代表上下左右固定的行与列数:[int,int,int,int]

6.支持指定任意行列的颜色,设置格式:[TableColor,TableColor,...],TableColor有两个子函数。设置行颜色的RowColor与设置列颜色的RowColor。

7.支持单元格点击回调,回调会返回所点击的单元格的对应数据对象T。

8.支持行列拖拽宽度和高度。

9.支持点击表头选中行列,并高亮。

10.手把手自定义事件处理。

  • 代码
import 'dart:async';

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

class KqTable<T extends ITableData> extends StatefulWidget {
  /// 控件数据
  final List<List<T>> data;

  /// 文本大小
  final double fontSize;

  /// 文本颜色
  final Color textColor;

  /// 控件宽度
  final double width;

  /// 控件高度
  final double height;

  /// 表格颜色
  final Color tableColor;

  /// 表格边框颜色
  final Color tableBorderColor;

  /// 表格点击选中颜色
  final Color tableClickChosenColor;

  /// 表格长按选中颜色
  final Color tableLongPressChosenColor;

  /// 表格点击选中边框颜色
  final Color tableClickChosenBorderColor;

  /// 表格长按选中边框颜色
  final Color tableLongPressChosenBorderColor;

  /// 上下左右固定行数值[int,int,int,int]
  final List<int> lockList;

  /// 指定特定行或者列的颜色,行使用[RowColor],列使用[ColumnColor]
  final List<TableColor> colorList;

  /// 点击单元格回调
  final Function(T data)? onTap;

  const KqTable(
      {super.key,
      required this.data,
      this.fontSize = 14,
      this.textColor = Colors.black,
      this.width = 300,
      this.height = 200,
      this.tableColor = Colors.white,
      this.tableBorderColor = const Color.fromARGB(255, 189, 189, 189),
      this.lockList = const [3, 0, 1, 0],
      this.colorList = const [
        RowColor(0, Color.fromARGB(255, 238, 238, 238)),
        RowColor(5, Color.fromARGB(255, 238, 238, 238)),
        RowColor(6, Color.fromARGB(255, 238, 238, 238)),
        ColumnColor(0, Color.fromARGB(255, 238, 238, 238)),
        ColumnColor(7, Color.fromARGB(255, 238, 238, 238))
      ],
      this.onTap,
      this.tableClickChosenColor =
          const Color.fromARGB(102, 130, 177, 255), //a=40% blue
      this.tableClickChosenBorderColor =
          const Color.fromARGB(230, 130, 177, 255), //90% blue
      this.tableLongPressChosenColor =
          const Color.fromARGB(102, 29, 233, 182), //a=40% green
      this.tableLongPressChosenBorderColor =
          const Color.fromARGB(230, 29, 233, 182)}); //a=90% green

  @override
  State<StatefulWidget> createState() => _KqTableState<T>();
}

class _KqTableState<T extends ITableData> extends State<KqTable<T>> {
  /// 长按判定等待时间100毫秒
  final int _waitTime = 100;

  /// x方向偏移量
  double _offsetDx = 0;

  /// y方向偏移量
  double _offsetDy = 0;

  /// x方向误差量
  double _diffOffsetDx = 0;

  /// y方向误差量
  double _diffOffsetDy = 0;

  /// 行数
  int _xLength = 0;

  /// 列数
  int _yLength = 0;

  /// 每列的行文本最大宽度列表[[原宽度,调整后宽度],[原宽度,调整后宽度],...]
  final List<List<double>> _xWidthList = [];

  /// 每行的文本高度,高度也可以变化,所以不能用一个值表达[[原高度,调整后高度],[原高度,调整后高度],...]
  final List<List<double>> _yHeightList = [];

  /// 按下时当前单元格的对象
  T? _opTableData;

  /// 当前手势是否滑动
  bool _opIsMove = false;

  /// 当前是否是长按
  bool _opIsLongPress = false;

  /// 绘制对象缓存
  final List<ITableData> _tempDrawData = <ITableData>[];

  /// 计时器
  Timer? timer;

  /// 点击选中行
  int _opClickChosenX = -1;

  /// 点击选中列
  int _opClickChosenY = -1;

  /// 长按选中行
  int _opLongPressChosenX = -1;

  /// 长按选中的行或者列的宽度或者高度值;
  double _opLongPressChosenWH = 0;

  /// 长按选中列
  int _opLongPressChosenY = -1;

  /// 点击是否同时选中行列
  bool _opIsClickChosenXY = false;

  /// 长按是否同时选中行列
  bool _opIsLongPressChosenXY = false;

  @override
  void initState() {
    super.initState();
    _initData();
  }

  @override
  void dispose() {
    //退出时关闭计时器防止内存泄露
    _stopLongPressTimer();
    super.dispose();
  }

  void _initData() {
    _xLength = widget.data[0].length;
    _yLength = widget.data.length;
    double columnHeight = 0;
    for (int i = 0; i < _xLength; i++) {
      double maxWidth = 0;
      for (int j = 0; j < _yLength; j++) {
        ITableData tableData = widget.data[j][i];
        TextPainter textPainter = TextPainter(
            text: TextSpan(
                text: tableData.text,
                style: TextStyle(
                    color: widget.textColor, fontSize: widget.fontSize)),
            maxLines: 1,
            textDirection: TextDirection.ltr)
          ..layout(minWidth: 0, maxWidth: double.infinity);
        if (maxWidth < textPainter.width) {
          maxWidth = textPainter.width;
        }
        columnHeight = textPainter.height;
      }
      _xWidthList.add([maxWidth, maxWidth]);
    }
    for (int j = 0; j < _yLength; j++) {
      _yHeightList.add([columnHeight, columnHeight]);
    }
  }

  void _startLongPressTimer(VoidCallback callback) {
    //计时器,每[_waitTime]毫秒执行一次
    var period = Duration(milliseconds: _waitTime);
    if (timer != null && timer!.isActive) {
      timer?.cancel();
    }
    timer = Timer(period, () {
      if (mounted) {
        _opIsLongPress = true;
        callback();
      }
    });
  }

  void _stopLongPressTimer() {
    timer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onVerticalDragUpdate: (_) {},
        child: RepaintBoundary(
            child: SizedBox(
          width: widget.width,
          height: widget.height,
          child: ClipRect(
              child: Listener(
            child: CustomPaint(
              painter: _TablePainter(
                  this,
                  _offsetDx,
                  _offsetDy,
                  widget.data,
                  _xLength,
                  _yLength,
                  _xWidthList,
                  _yHeightList,
                  _tempDrawData,
                  _opClickChosenX,
                  _opClickChosenY,
                  _opLongPressChosenX,
                  _opLongPressChosenY,
                  _opIsClickChosenXY,
                  _opIsLongPressChosenXY),
            ),
            onPointerDown: (PointerDownEvent event) {
              _opIsMove = false;
              _opIsLongPress = false;
              _opIsLongPressChosenXY = false;
              _opIsClickChosenXY = false;
              _opLongPressChosenX = -1;
              _opLongPressChosenY = -1;
              _opClickChosenX = -1;
              _opClickChosenY = -1;
              //事件点击的中心位置
              Offset? eventOffset = event.localPosition;
              _diffOffsetDx = eventOffset.dx - _offsetDx;
              _diffOffsetDy = eventOffset.dy - _offsetDy;

              ///判定按下在哪个单元格,并获取单元格内容
              //点击的横向坐标
              int y = 0;
              //点击的纵向坐标
              int x = 0;
              //计算横向和纵向坐标
              ITableData? tempX;
              ITableData? tempY;
              for (ITableData tableData in _tempDrawData) {
                if (eventOffset.dx < (tableData.left! + tableData.width!) &&
                    eventOffset.dx > tableData.left!) {
                  if (tempX == null || tempX.level! < tableData.level!) {
                    tempX = tableData;
                  }
                }

                if (eventOffset.dy < (tableData.top! + tableData.height!) &&
                    eventOffset.dy > tableData.top!) {
                  if (tempY == null || tempY.level! < tableData.level!) {
                    tempY = tableData;
                  }
                }
              }
              if (tempX != null) {
                x = tempX.x!;
              }
              if (tempY != null) {
                y = tempY.y!;
              }
              // 单击单元格判定
              if (x == 0 && y == 0) {
                _opIsClickChosenXY = true;
              } else if (x == 0) {
                _opClickChosenY = y;
              } else if (y == 0) {
                _opClickChosenX = x;
              } else {
                _opIsClickChosenXY = false;
                _opClickChosenX = -1;
                _opClickChosenY = -1;
              }
              //获取坐标对应的值
              _opTableData = widget.data[y][x];

              /// 长按拖拽判定
              _startLongPressTimer(() {
                if (y == 0 && x != 0) {
                  // 判断宽度拖拽
                  _opLongPressChosenX = x;
                  _opLongPressChosenWH = _xWidthList[_opLongPressChosenX][1];
                } else if (x == 0 && y != 0) {
                  //判断高度拖拽
                  _opLongPressChosenY = y;
                  _opLongPressChosenWH = _yHeightList[_opLongPressChosenY][1];
                } else if (y == 0 && x == 0) {
                  //判断宽度和高度同时拖拽
                  _opIsLongPressChosenXY = true;
                }
                if (_opLongPressChosenX != -1 ||
                    _opLongPressChosenY != -1 ||
                    _opIsLongPressChosenXY) {
                  _opClickChosenX = -1;
                  _opClickChosenY = -1;
                  _opIsClickChosenXY = false;
                }
                setState(() {});
              });
            },
            onPointerMove: (PointerMoveEvent event) {
              _opIsMove = true;
              _stopLongPressTimer();
              //事件点击的中心位置
              Offset? eventOffset = event.localPosition;
              if (_opLongPressChosenX != -1) {
                ///表格宽度拖拽
                if (_xWidthList[_opLongPressChosenX][1] >
                        _xWidthList[_opLongPressChosenX][0] ||
                    ((eventOffset.dx - _diffOffsetDx) > 0 &&
                        _xWidthList[_opLongPressChosenX][1] ==
                            _xWidthList[_opLongPressChosenX][0])) {
                  _xWidthList[_opLongPressChosenX][1] =
                      _opLongPressChosenWH + eventOffset.dx - _diffOffsetDx;
                } else {
                  _xWidthList[_opLongPressChosenX][1] =
                      _xWidthList[_opLongPressChosenX][0];
                }
              } else if (_opLongPressChosenY != -1) {
                ///表格高度拖拽
                if (_yHeightList[_opLongPressChosenY][1] >
                    _yHeightList[_opLongPressChosenY][0] ||
                    ((eventOffset.dy - _diffOffsetDy) > 0 &&
                        _yHeightList[_opLongPressChosenY][1] ==
                            _yHeightList[_opLongPressChosenY][0])) {
                  _yHeightList[_opLongPressChosenY][1] =
                      _opLongPressChosenWH + eventOffset.dy - _diffOffsetDy;
                } else {
                  _yHeightList[_opLongPressChosenY][1] =
                      _yHeightList[_opLongPressChosenY][0];
                }
              } else if (_opIsLongPressChosenXY) {
                ///宽高同时拖拽
                if (eventOffset.dx >= _xWidthList[0][0]) {
                  _xWidthList[0][1] = eventOffset.dx;
                } else {
                  _xWidthList[0][1] = _xWidthList[0][0];
                }
                if (eventOffset.dy >= _yHeightList[0][0]) {
                  _yHeightList[0][1] = eventOffset.dy;
                } else {
                  _yHeightList[0][1] = _yHeightList[0][0];
                }
              } else {
                ///表格移动
                _offsetDx = eventOffset.dx - _diffOffsetDx;
                _offsetDy = eventOffset.dy - _diffOffsetDy;
              }

              /// 边界处理
              // 当有固定行时
              // 上边限定
              if (_offsetDy >= 0) {
                _offsetDy = 0;
              }
              // 左边限定
              if (_offsetDx >= 0) {
                _offsetDx = 0;
              }
              // 右边限定
              double rightOffset = 0;
              double tableWidth = _TableUtils.getTableRealWidth(_xWidthList);
              double tableHeight = _TableUtils.getTableRealHeight(_yHeightList);
              for (int i = 0; i < widget.lockList[3]; i++) {
                rightOffset += _xWidthList[_xWidthList.length - i - 1][1];
              }
              if (_offsetDx <= (widget.width + rightOffset) - tableWidth) {
                _offsetDx = (widget.width + rightOffset) - tableWidth;
              }
              // 下边限定
              List<double> reversalCellHeights =
                  _TableUtils.reversalCellHeights(_yLength, _yHeightList);
              if (_offsetDy <=
                  (widget.height +
                          reversalCellHeights[widget.lockList[1] == 0
                              ? 0
                              : widget.lockList[1] - 1]) -
                      tableHeight) {
                _offsetDy = (widget.height +
                        reversalCellHeights[widget.lockList[1] == 0
                            ? 0
                            : widget.lockList[1] - 1]) -
                    tableHeight;
              }
              //当表格宽度小于控件宽度,则不能水平移动
              if (tableWidth <= widget.width) {
                _offsetDx = 0;
              }
              //当表格高度小于控件高度,则不能上下移动
              if (tableHeight <= widget.height) {
                _offsetDy = 0;
              }

              setState(() {});
            },
            onPointerUp: (PointerUpEvent event) {
              if (_opIsLongPress) {
                //长按
                setState(() {
                  _opLongPressChosenX = -1;
                  _opLongPressChosenY = -1;
                  _opIsLongPressChosenXY = false;
                });
              } else if (!_opIsMove) {
                //单击
                setState(() {
                  _stopLongPressTimer();
                  widget.onTap?.call(_opTableData as T);
                });
              }
            },
          )),
        )));
  }
}

class _TablePainter<T> extends CustomPainter {
  /// state
  final _KqTableState state;

  /// x方向偏移量
  final double _offsetDx;

  /// y方向偏移量
  final double _offsetDy;

  final List<List<T>>? _data;

  /// 行数
  final int _xLength;

  /// 列数
  final int _yLength;

  /// 每列的行文本最大宽度列表
  final List<List<double>> _xWidthList;

  /// 每行的文本高度列表
  final List<List<double>> _columnHeightList;

  /// 绘制对象缓存
  final List<ITableData> _tempDrawData;

  /// 点击选中行
  final int _opClickChosenX;

  /// 点击选中列
  final int _opClickChosenY;

  /// 长按选中行
  final int _opLongPressChosenX;

  /// 长按选中列
  final int _opLongPressChosenY;

  /// 点击是否同时选中行列
  final bool _opIsClickChosenXY;

  /// 长按是否同时选中行列
  final bool _opIsLongPressChosenXY;

  _TablePainter(
      this.state,
      this._offsetDx,
      this._offsetDy,
      this._data,
      this._xLength,
      this._yLength,
      this._xWidthList,
      this._columnHeightList,
      this._tempDrawData,
      this._opClickChosenX,
      this._opClickChosenY,
      this._opLongPressChosenX,
      this._opLongPressChosenY,
      this._opIsClickChosenXY,
      this._opIsLongPressChosenXY);

  @override
  void paint(Canvas canvas, Size size) {
    //表格边框画笔
    final Paint paint1 = Paint()
      ..strokeCap = StrokeCap.square
      ..isAntiAlias = true
      ..style = PaintingStyle.stroke
      ..color = state.widget.tableBorderColor;
    //表格背景画笔
    final Paint paint2 = Paint()
      ..strokeCap = StrokeCap.square
      ..isAntiAlias = true
      ..style = PaintingStyle.fill
      ..color = state.widget.tableColor;

    _tempDrawData.clear();
    drawTable(canvas, size, paint1, paint2);
  }

  void drawTable(Canvas canvas, Size size, Paint paint1, Paint paint2) {
    List<double> reversalRowWidths =
        _TableUtils.reversalCellWidths(_xLength, _xWidthList);
    List<double> reversalColumnHeights =
        _TableUtils.reversalCellHeights(_yLength, _columnHeightList);
    double totalCellWidth = 0;
    double cellWidth = 0;
    for (int i = 0; i < _xLength; i++) {
      totalCellWidth += cellWidth;
      cellWidth = _xWidthList[i][1];
      double totalCellHeight = 0;
      double cellHeight = 0;
      if (totalCellWidth + _offsetDx <= state.widget.width) {
        for (int j = 0; j < _yLength; j++) {
          String str = (_data![j][i] as ITableData).text;
          totalCellHeight += cellHeight;
          cellHeight = _columnHeightList[j][1];
          if (totalCellHeight + _offsetDy <= state.widget.height) {
            if (j < state.widget.lockList[0]) {
              //上
              if (i < state.widget.lockList[2]) {
                //左上角
                drawTableAdd(str, totalCellWidth, totalCellHeight, cellWidth,
                    cellHeight, j, i,
                    level: 2);
              } else if (i >= _xLength - state.widget.lockList[3]) {
                //右上角
                drawTableAdd(
                    str,
                    state.widget.width - reversalRowWidths[_xLength - i - 1],
                    totalCellHeight,
                    cellWidth,
                    cellHeight,
                    j,
                    i,
                    level: 2);
              } else {
                drawTableAdd(str, totalCellWidth + _offsetDx, totalCellHeight,
                    cellWidth, cellHeight, j, i,
                    level: 1);
              }
            } else if (i < state.widget.lockList[2]) {
              //左
              if (j >= _yLength - state.widget.lockList[1]) {
                //左下角
                drawTableAdd(
                    str,
                    totalCellWidth,
                    state.widget.height -
                        reversalColumnHeights[_yLength - j - 1],
                    cellWidth,
                    cellHeight,
                    j,
                    i,
                    level: 2);
              } else {
                drawTableAdd(str, totalCellWidth, totalCellHeight + _offsetDy,
                    cellWidth, cellHeight, j, i,
                    level: 1);
              }
            } else if (j >= _yLength - state.widget.lockList[1]) {
              //下
              if (i >= _xLength - state.widget.lockList[3]) {
                // 右下角
                drawTableAdd(
                    str,
                    state.widget.width - reversalRowWidths[_xLength - i - 1],
                    state.widget.height -
                        reversalColumnHeights[_yLength - j - 1],
                    cellWidth,
                    cellHeight,
                    j,
                    i,
                    level: 2);
              } else {
                drawTableAdd(
                    str,
                    totalCellWidth + _offsetDx,
                    state.widget.height -
                        reversalColumnHeights[_yLength - j - 1],
                    cellWidth,
                    cellHeight,
                    j,
                    i,
                    level: 1);
              }
            } else if (i >= _xLength - state.widget.lockList[3]) {
              //右
              drawTableAdd(
                  str,
                  state.widget.width - reversalRowWidths[_xLength - i - 1],
                  totalCellHeight + _offsetDy,
                  cellWidth,
                  cellHeight,
                  j,
                  i,
                  level: 1);
            } else {
              drawTableAdd(str, totalCellWidth + _offsetDx,
                  totalCellHeight + _offsetDy, cellWidth, cellHeight, j, i);
            }
          }
        }
      }
    }

    drawTableReal(canvas, size, paint1, paint2);
  }

  /// 把需要绘制的数据先放入内存中
  void drawTableAdd(String text, double left, double top, double width,
      double height, int y, int x,
      {int? level}) {
    if (top <= state.widget.height && left <= state.widget.width) {
      _tempDrawData.add(ITableData(text,
          left: left,
          top: top,
          width: width,
          height: height,
          y: y,
          x: x,
          level: level ?? 0));
    }
  }

  /// 遍历存好的数据进行绘制
  void drawTableReal(Canvas canvas, Size size, Paint paint1, Paint paint2) {
    //绘制层级排序
    _tempDrawData.sort((a, b) => a.level!.compareTo(b.level!));
    //绘制
    for (ITableData data in _tempDrawData) {
      if (data.top! <= state.widget.height &&
          data.left! <= state.widget.width) {
        //构建文字
        ui.ParagraphBuilder paragraphBuilder =
            ui.ParagraphBuilder(ui.ParagraphStyle())
              ..pushStyle(ui.TextStyle(
                  color: state.widget.textColor, fontSize: state.widget.fontSize))
              ..addText(data.text);
        //先初始化paint2的颜色
        paint2.color = state.widget.tableColor;
        //表格有指定颜色的颜色
        if (state.widget.colorList.isNotEmpty) {
          for (TableColor tableColor in state.widget.colorList) {
            if (tableColor is RowColor && tableColor.index == data.y) {
              paint2.color = tableColor.color;
            } else if (tableColor is ColumnColor &&
                tableColor.index == data.x) {
              paint2.color = tableColor.color;
            }
          }
        }

        ///画表格背景
        canvas.drawRect(
            Rect.fromLTWH(data.left!, data.top!, data.width!, data.height!),
            paint2);

        //画笔颜色调整,主要针点击背景覆盖层和边框绘制
        if ((_opLongPressChosenX != -1 && data.x == _opLongPressChosenX) ||
            (_opLongPressChosenY != -1 && data.y == _opLongPressChosenY) ||
            (_opIsLongPressChosenXY && (data.x == 0 || data.y == 0))) {
          paint2.color = state.widget.tableLongPressChosenColor;
          paint1.color = state.widget.tableLongPressChosenBorderColor;

          ///画表格覆盖背景
          canvas.drawRect(
              Rect.fromLTWH(data.left!, data.top!, data.width!, data.height!),
              paint2);
        } else if ((_opClickChosenX != -1 && data.x == _opClickChosenX) ||
            (_opClickChosenY != -1 && data.y == _opClickChosenY) ||
            (_opIsClickChosenXY && (data.x == 0 || data.y == 0))) {
          paint2.color = state.widget.tableClickChosenColor;
          paint1.color = state.widget.tableClickChosenBorderColor;

          ///画表格覆盖背景
          canvas.drawRect(
              Rect.fromLTWH(data.left!, data.top!, data.width!, data.height!),
              paint2);
        }

        ///画表格边框
        canvas.drawRect(
            Rect.fromLTWH(data.left!, data.top!, data.width!, data.height!),
            paint1);

        ///画表格文本
        canvas.drawParagraph(
            paragraphBuilder.build()
              ..layout(ui.ParagraphConstraints(width: size.width)),
            Offset(data.left!, data.top!));
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

abstract class TableColor {
  final int index;
  final Color color;

  const TableColor(this.index, this.color);
}

class RowColor extends TableColor {
  const RowColor(super.index, super.color);
}

class ColumnColor extends TableColor {
  const ColumnColor(super.index, super.color);
}

class ITableData {
  final String text;
  double? left;
  double? top;
  double? width;
  double? height;
  int? y;
  int? x;
  int? level = 0;

  ITableData(this.text,
      {this.left,
      this.top,
      this.width,
      this.height,
      this.y,
      this.x,
      this.level});

  @override
  String toString() {
    return "text=$text,left=$left,top=$top,width=$width,height=$height,row=$y,column=$x,level=$level";
  }
}

class _TableUtils {
  /// 单元格宽度反向长度列表
  static List<double> reversalCellWidths(
      int xLength, List<List<double>> xWidthList) {
    List<double> totalReversalCellWidthList = [];
    double totalReversalCellWidth = 0;
    for (int i = xLength - 1; i >= 0; i--) {
      totalReversalCellWidth += xWidthList[i][1];
      totalReversalCellWidthList.add(totalReversalCellWidth);
    }
    return totalReversalCellWidthList;
  }

  /// 单元格高度反向高度列表
  static List<double> reversalCellHeights(
      int yLength, List<List<double>> yHeightList) {
    List<double> totalReversalCellHeightList = [];
    double totalReversalCellHeight = 0;
    for (int i = yLength - 1; i >= 0; i--) {
      totalReversalCellHeight += yHeightList[i][1];
      totalReversalCellHeightList.add(totalReversalCellHeight);
    }
    return totalReversalCellHeightList;
  }

  /// 获取表格宽高
  static double getTableRealWidth(List<List<double>> xWidthList) {
    double totalWidth = 0;
    for (int i = 0; i < xWidthList.length; i++) {
      totalWidth += xWidthList[i][1];
    }
    return totalWidth;
  }

  /// 获取表格宽高
  static double getTableRealHeight(List<List<double>> yHeightList) {
    double totalHeight = 0;
    for (int i = 0; i < yHeightList.length; i++) {
      totalHeight += yHeightList[i][1];
    }
    return totalHeight;
  }
}
  • 使用

构建测试数据:

class TestTableData extends ITableData {
  TestTableData(super.text);
}


List<List<TestTableData>> _getTableTestData2() {
    //模拟数据
    List<List<TestTableData>> data = [];
    Random random = Random();

    for (int i = 0; i < 20; i++) {
      List<TestTableData> dataList = [];
      for (int j = 0; j < 10; j++) {
        int seed = random.nextInt(100);
        dataList.add(TestTableData(" $seed "));
      }
      data.add(dataList);
    }
    return data;
}

使用:

KqTable<TestTableData>(
    data: _getTableTestData2(),
    onTap: (TestTableData data) {
        KqToast.showNormal(data.text);
    },
)

代码注释全面,有需要的朋友可以直接撸,如有问题,欢迎指正。


http://www.kler.cn/a/685.html

相关文章:

  • KAGGLE竞赛实战2-捷信金融违约预测竞赛-part1-数据探索及baseline建立
  • 第15章 汇编语言--- 数组与指针
  • 自动驾驶三维重建
  • 一文讲清楚HTTP常见的请求头和应用
  • 个人交友系统|Java|SSM|JSP|
  • 以太网协议和LWIP协议详解
  • linux目录——文件管理
  • 【C#】组件化开发,调用dll组件方法
  • UE笔记-AI Move To无法正常结束/打断 1
  • 这两天最好的ChatGPT应用;使用Notion AI提升效率的经验(13);AI编程与程序员的生存 | ShowMeAI日报
  • 数据库基础语法
  • 三天吃透计算机网络面试八股文
  • stm32外设-GPIO
  • 802.1x认证和MAC认证讲解
  • Towards Unsupervised Text Classification Leveraging Experts and Word Embeddings
  • 信息时代的必修课:信息增量(利用相关性进行视频压缩编码)
  • 【云原生】Linux进程控制(创建、终止、等待)
  • 一年经验年初被裁面试1月有余无果,还遭前阿里面试官狂问八股,人麻了
  • 【笔记】效率之门——Python中的函数式编程技巧
  • 【CSS】盒子模型内边距 ② ( 内边距复合写法 | 代码示例 )
  • 【数据结构】第二站:顺序表
  • 网站动态背景 | vanta.js的使用
  • Go中指针的介绍和使用
  • 分享几个常用的运维 shell 脚本
  • 春分策划×运维老王主讲:CMDB数据运营精准化公开课启动报名啦!
  • 常见的Web安全漏洞:SYN攻击/CSRF/XSS