【Flutter】封装表格Table,实现固定表头、固定列功能、自定义单元格表头等功能

题记

Flutter 自带的Table 组件或者是 DataTable 组件,总是不符合预期需求。主要体现在滑动的时候,想要固定表头、或者固定某一列的时候会比较麻烦。然而我们的 toB 项目设计师喜欢设计很多列的表格,emmmm 好吧,封装一个通用点的开源出来😭。

pub.dev/packages/st...

效果图

如图: 可以看到名称这一列和操作这一列是可以固定的,然后中间的数据是可以自由左右滚动的。然后向下滚动的时候标题栏是一直固定在顶部,内容可以正常垂直滚动。

所以实现这么一个表格,需要考虑下面这些点:

  • 固定列,同时内容是同时滚动的
  • 固定表头,表头不会随着内容垂直滚动,但是左右滚动的时候会跟随(除了固定的列)。
  • 自定义表头
  • 自定义单元格
  • 使用简单,支持各种数据

简单使用

以下是封装后的组件,使用方式。

dart 复制代码
demo() {
  ///简单用法
  StickyTable(
      data: [1, 2, 3, 4, 5],
      columns: [
        StickyTableColumn(
          "Title",
          //固定在开头
          fixedStart: true,
          //固定在结尾
          fixedEnd: false,
        )
      ]
  );
}

实现思路

Table/ 布局思路

在不考虑滚动联动的情况下,我们布局大致实现可以如下,(伪代码):

dart 复制代码
Column(
 children:[
    //单独渲染一次表头
    Row(
       children:[
           //固定的列
           if( 存在前面固定的列数据 fixedColumnData)
           TableColumn(fixedColumnData),
           //可滚动的列
           Expanded(
              child: SingleChildScrollView(
                controller: scrollControl.addAndGet(onlyTitle ? "title" : "body"),
                scrollDirection: Axis.horizontal,
                physics: const ClampingScrollPhysics(),
                child:TableColumn(columnData),
            ),
           ),
           //固定的列,在后面的
           if(存在后面固定的列数据 fixedEndColumnData)
           TableColumn(fixedEndColumnData),
       ]
    ),
    //渲染表格内容
    Expanded(
      child: SingleChildScrollView(
        child: Row(
           children:[
               //渲染方式应该和上面的表头一致
               //固定的列
               if( 存在前面固定的列数据 fixedColumnData)
               TableColumn(fixedColumnData),
               //可滚动的列
               Expanded(
                  child: SingleChildScrollView(
                    controller: scrollControl.addAndGet(onlyTitle ? "title" : "body"),
                    scrollDirection: Axis.horizontal,
                    physics: const ClampingScrollPhysics(),
                    child:TableColumn(columnData),
                ),
               ),
               //固定的列,在后面的
               if(存在后面固定的列数据 fixedEndColumnData)
               TableColumn(fixedEndColumnData),
           ]
        ),
      ),
    )
 ],
)

需要解决问题

上述布局中,可以发现,已经解决了垂直滚动、和固定表头、固定列的逻辑。但是标题栏的可滚动区域和内容的可滚动区域是不同的SingleChildScrollView,所以主要问题就是如何让这两个ScrollView同步滚动。

这里有两个外部库,可任选其一:

pub.dev/packages/sy... pub.dev/packages/li...

我是在这个第三方库的基础上修改了下,以适配自己的组件。

用法很简单,通过以下方式即可绑定不同的ScrollView然后进行同步滚动了。

dart 复制代码
final _controllers = LinkedScrollControllerGroup();
final  _letters = _controllers.addAndGet();
final  _numbers = _controllers.addAndGet();

SingleChildScrollView(
    controller: _letters.
),
SingleChildScrollView(
    controller: _numbers.
),

最后渲染表格

将我们的业务数据转换到 Table 中

less 复制代码
Table(
 children:[
    //通过判断是否只渲染表头,来渲染内容
    if(onlyRenderTitle)
        TableRow(
           children:allColumnData.map((e) => Text("标题")).toList()
        )
    else
      // 渲染每一行
        for (var row = 0; row < widget.data.length; row++) ...[
          TableRow(
            children:allColumnData.map((e) => Text("内容")).toList()
          )
  ]
)

以上为实现的核心逻辑,其他的主要通过数据渲染组件的简单逻辑,没什么好讲的了,有兴趣的可以自行去扒拉我的源码,仓库在 git,就一个类文件,下面介绍下封装后的组件如何使用。

GIT 地址: github.com/AlwaysSum/f...

使用

安装

arduino 复制代码
flutter pub get sticky_table

如何固定表头

less 复制代码
demo() {
  StickyTable(
      data: [1, 2, 3, 4, 5],
      columns: [
        StickyTableColumn(
          "Title",
          //固定在开头
          fixedStart: true,
          //固定在结尾
          fixedEnd: false,
        )
      ]
  );
}

显示斑马线

javascript 复制代码
demo() {
  StickyTable(
    showZebraCrossing: true,
    zebraCrossingColor: (Colors.white, const Color(0xfff5f5f5)),
    zebraCrossingRadius: const Radius.circular(12),
  );
}

较为完善的使用示例

less 复制代码
 demo() {
  return StickyTable(
    // 支持任意数据数组
    data: List.generate(50, (index) => sort ? 50 - index : index),
    // 默认列宽
    defaultColumnWidth: const FixedColumnWidth(80),
    // 标题的高度 default: 58
    titleHeight: 58,
    // 单元格高度 default: 58
    cellHeight: 58,
    // 每一列的配置
    columns: [
      StickyTableColumn(
        "Title", // 标题 / TITLE
        fixedStart: true,
        //是否在开头固定 / Is it fixed at the beginning?
        showSort: true,
        //是否支持排序 /Support sorting
        sort: sort,
        //排序图标 / Sort i
        //列宽:支持百分比和自适应 / Column width, Support percentage and adaptive
        columnWidth: const FixedColumnWidth(80),
        //单元格的对齐方式 /Align cells
        alignment: Alignment.centerLeft,
        // 标题的点击时间 / Click time of the title
        onTitleClick: (context, title) {
          ScaffoldMessenger.of(context).hideCurrentSnackBar();
          ScaffoldMessenger.of(context)
              .showSnackBar(const SnackBar(content: Text("Sort")));
          setState(() {
            sort = !(title.sort ?? false);
          });
        },
        // 自定义渲染单元格 / Custom rendering cells
        renderCell: (context, title, data, row, column) {
          return Text("Name$data");
        },
        //自定义渲染标题 / Custom rendering title
        renderTitle: (context, title) {
          return Text(
            title.title,
            style: const TextStyle(color: Colors.purple),
          );
        },
      ),
      StickyTableColumn(
        "Age",
        showSort: true,
        sort: false,
        onCellClick: (context, title, data, row, column) {
          ScaffoldMessenger.of(context).hideCurrentSnackBar();
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text("年龄$data")));
        },
        renderCell: (context, title, data, row, column) {
          return Text("*$data");
        },
      ),
      ...List.generate(
        10,
            (index) =>
            StickyTableColumn(
              "Sub Title $index",
              showSort: true,
              sort: false,
              renderCell: (context, title, data, row, column) {
                return Text("Content $row-$column");
              },
            ),
      ),
      StickyTableColumn(
        "Option",
        // 固定在结尾
        fixedEnd: true,
        columnWidth: const FixedColumnWidth(100),
        renderCell: (context, title, data, row, column) {
          return MaterialButton(
            onPressed: () {
              ScaffoldMessenger.of(context).hideCurrentSnackBar();
              ScaffoldMessenger.of(context)
                  .showSnackBar(SnackBar(content: Text("删除$data成功")));
            },
            color: Colors.red,
            minWidth: 0,
            child: const Text(
              "Delete",
              style: TextStyle(color: Colors.white),
            ),
          );
        },
      ),
    ],
  );
}

Tanks~

相关推荐
Beekeeper&&P...7 分钟前
web钩子什么意思
前端·网络
暮志未晚Webgl8 分钟前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
啵咿傲19 分钟前
重绘&重排、CSS树&DOM树&渲染树、动画加速 ✅
前端·css
前端Hardy21 分钟前
HTML&CSS:数据卡片可以这样设计
前端·javascript·css·3d·html
流烟默25 分钟前
CSS中Flex布局应用实践总结
前端·css·flex布局
麦田里的守望者江30 分钟前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-43 分钟前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记
草字1 小时前
uniapp input限制输入负数,以及保留小数点两位.
java·前端·uni-app
没有黑科技1 小时前
基于web的音乐网站(Java+SpringBoot+Mysql)
java·前端·spring boot
佛系小嘟嘟1 小时前
Android Studio不显示需要的tag日志解决办法《All logs entries are hidden by the filter》
android·ide·android studio