You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Flutter DataTable表头固定、内容可滚动的效果?

Flutter原生实现固定表头、内容可滚动的DataTable(无需第三方包)

要实现类似NG-Zorro Table的固定表头效果,无需依赖第三方包,核心是将表头与表格内容拆分,让表头固定在顶部,内容区域单独滚动。以下是基于你现有代码的改造方案:

import 'package:flutter/material.dart';

class FixedHeaderDataTable extends StatelessWidget {
  const FixedHeaderDataTable({super.key});

  // 统一管理列配置,确保表头与内容布局对齐
  final List<Map<String, dynamic>> columnConfigs = [
    {
      'title': 'header 1',
      'padding': const EdgeInsets.only(left: 20),
    },
    {
      'title': 'header 2',
      'padding': EdgeInsets.zero,
    },
    {
      'title': 'header 3',
      'padding': EdgeInsets.zero,
    },
    {
      'title': 'header 4',
      'padding': EdgeInsets.zero,
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 32),
      child: Card(
        margin: EdgeInsets.zero,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 固定表头区域
            Container(
              color: Colors.grey[50],
              child: Row(
                children: [
                  // 对应DataTable的复选框列
                  const Padding(
                    padding: EdgeInsets.symmetric(horizontal: 6),
                    child: SizedBox(
                      width: 24,
                      child: Checkbox(value: false, onChanged: null),
                    ),
                  ),
                  // 遍历列配置生成表头项
                  ...columnConfigs.map((config) {
                    return Expanded(
                      flex: 1,
                      child: Padding(
                        padding: config['padding'] as EdgeInsets,
                        child: Text(
                          config['title'] as String,
                          style: const TextStyle(
                            fontSize: 12,
                            color: Colors.grey,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                      ),
                    );
                  }),
                ],
              ),
            ),
            // 可滚动的表格内容区域
            Expanded(
              child: SingleChildScrollView(
                padding: EdgeInsets.zero,
                child: SizedBox(
                  width: double.infinity,
                  child: CheckboxTheme(
                    data: CheckboxThemeData(
                      side: MaterialStateBorderSide.resolveWith(
                        (_) => const BorderSide(width: 1, color: Colors.blueGrey),
                      ),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(5),
                      ),
                      fillColor: MaterialStateProperty.all(Colors.blueGrey[25]),
                      checkColor: MaterialStateProperty.all(Colors.blueGrey[700]),
                    ),
                    child: DataTable(
                      columnSpacing: 12,
                      // 隐藏原生表头,避免重复
                      headingRowHeight: 0,
                      dataRowHeight: 60,
                      checkboxHorizontalMargin: 5,
                      showCheckboxColumn: true,
                      horizontalMargin: 6,
                      // 保留列配置保证内容列与表头对齐
                      columns: columnConfigs.map((config) {
                        return DataColumn(
                          label: Padding(
                            padding: config['padding'] as EdgeInsets,
                            child: const SizedBox.shrink(),
                          ),
                        );
                      }).toList(),
                      rows: [
                        for (int x = 0; x < 20; x++)
                          DataRow(
                            color: MaterialStateProperty.resolveWith((states) {
                              if (states.contains(MaterialState.selected)) {
                                return Colors.transparent;
                              }
                              return Colors.transparent;
                            }),
                            cells: [
                              DataCell(
                                Padding(
                                  padding: const EdgeInsets.only(left: 20),
                                  child: Text(
                                    "data 1",
                                    style: TextStyle(
                                      fontSize: 14,
                                      color: Colors.grey[900],
                                      fontWeight: FontWeight.w500,
                                    ),
                                  ),
                                ),
                              ),
                              DataCell(
                                Text(
                                  "data 2",
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey[900],
                                    fontWeight: FontWeight.w500,
                                  ),
                                ),
                              ),
                              DataCell(
                                Text(
                                  "data 3",
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey[900],
                                    fontWeight: FontWeight.w500,
                                  ),
                                ),
                              ),
                              DataCell(
                                Text(
                                  "data 4",
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey[900],
                                    fontWeight: FontWeight.w500,
                                  ),
                                ),
                              ),
                            ],
                          )
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键改动说明

  • 统一列配置:将列标题、内边距等属性抽成统一列表,确保表头和内容列的布局完全对齐,避免出现错位。
  • 自定义固定表头:用Container+Row模拟原生DataTable的表头样式,包含复选框列和所有数据列,固定在Card顶部。
  • 隐藏原生表头:设置DataTable的headingRowHeight: 0,彻底隐藏自带表头,避免重复渲染。
  • 独立内容滚动:仅将表格的行数据区域包裹在SingleChildScrollView中,实现内容纵向滚动时表头保持固定。

如果需要支持横向滚动,可在SingleChildScrollView中添加scrollDirection: Axis.horizontal,同时将表头也放入同一个横向滚动容器内,即可实现横向滚动时表头与内容同步移动。

内容的提问来源于stack exchange,提问作者Alfahad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 16:15:15