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

Flutter DataTable固定顶部行、左右列实现方案咨询

实现Flutter DataTable表头、左右列冻结方案

原生DataTable没有内置冻结列/表头的功能,我们可以通过组合SingleChildScrollView、Row、Column等基础Widget,配合滚动控制器同步来实现需求。

核心实现思路

  • 将表格拆分为顶部固定表头、左侧固定列、中间可滚动列、右侧固定列四个模块
  • 用垂直ScrollController同步左侧、中间、右侧列的垂直滚动位置,保证行对齐
  • 中间列单独用水平SingleChildScrollView实现横向滚动

完整代码示例

import 'package:flutter/material.dart';

class FrozenDataTable extends StatefulWidget {
  const FrozenDataTable({super.key});

  @override
  State<FrozenDataTable> createState() => _FrozenDataTableState();
}

class _FrozenDataTableState extends State<FrozenDataTable> {
  // 同步垂直滚动的控制器
  final ScrollController _verticalController = ScrollController();
  // 模拟表格数据
  final List<List<String>> _tableData = List.generate(
    50,
    (index) => [
      '行$index',
      '数据${index}A',
      '数据${index}B',
      '数据${index}C',
      '数据${index}D',
      '数据${index}E',
      '固定右列$index',
    ],
  );

  // 统一行高,避免滚动错位
  static const double _rowHeight = 56;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('冻结列示例')),
      body: Column(
        children: [
          // 顶部固定表头
          _buildHeaderRow(),
          // 表格内容区域(垂直滚动)
          Expanded(
            child: SingleChildScrollView(
              controller: _verticalController,
              child: Column(
                children: List.generate(
                  _tableData.length,
                  (rowIndex) => SizedBox(
                    height: _rowHeight,
                    child: Row(
                      children: [
                        // 左侧固定列
                        _buildFixedLeftCell(_tableData[rowIndex][0]),
                        // 中间可滚动列
                        SizedBox(
                          width: MediaQuery.of(context).size.width - 240, // 减去左右固定列宽度
                          child: SingleChildScrollView(
                            scrollDirection: Axis.horizontal,
                            child: Row(
                              children: _tableData[rowIndex]
                                  .sublist(1, 6)
                                  .map((cell) => _buildScrollableCell(cell))
                                  .toList(),
                            ),
                          ),
                        ),
                        // 右侧固定列
                        _buildFixedRightCell(_tableData[rowIndex][6]),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  // 构建表头行
  Widget _buildHeaderRow() {
    return SizedBox(
      height: _rowHeight,
      child: Row(
        children: [
          _buildFixedLeftCell('固定左列'),
          SizedBox(
            width: MediaQuery.of(context).size.width - 240,
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: const [
                  _HeaderCell(text: '列A'),
                  _HeaderCell(text: '列B'),
                  _HeaderCell(text: '列C'),
                  _HeaderCell(text: '列D'),
                  _HeaderCell(text: '列E'),
                ],
              ),
            ),
          ),
          _buildFixedRightCell('固定右列'),
        ],
      ),
    );
  }

  // 左侧固定单元格
  Widget _buildFixedLeftCell(String text) {
    return Container(
      width: 120,
      height: _rowHeight,
      alignment: Alignment.centerLeft,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      decoration: const BoxDecoration(
        border: Border(
          right: BorderSide(color: Colors.grey),
          bottom: BorderSide(color: Colors.grey),
        ),
        color: Colors.grey[50],
      ),
      child: Text(text, style: const TextStyle(fontWeight: FontWeight.bold)),
    );
  }

  // 右侧固定单元格
  Widget _buildFixedRightCell(String text) {
    return Container(
      width: 120,
      height: _rowHeight,
      alignment: Alignment.centerLeft,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      decoration: const BoxDecoration(
        border: Border(
          left: BorderSide(color: Colors.grey),
          bottom: BorderSide(color: Colors.grey),
        ),
        color: Colors.grey[50],
      ),
      child: Text(text),
    );
  }

  // 中间滚动单元格
  Widget _buildScrollableCell(String text) {
    return Container(
      width: 150,
      height: _rowHeight,
      alignment: Alignment.centerLeft,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      decoration: const BoxDecoration(
        border: Border(
          right: BorderSide(color: Colors.grey),
          bottom: BorderSide(color: Colors.grey),
        ),
      ),
      child: Text(text),
    );
  }
}

// 表头单元格组件
class _HeaderCell extends StatelessWidget {
  final String text;
  const _HeaderCell({required this.text});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 150,
      height: _FrozenDataTableState._rowHeight,
      alignment: Alignment.centerLeft,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      decoration: const BoxDecoration(
        border: Border(
          right: BorderSide(color: Colors.grey),
          bottom: BorderSide(color: Colors.grey),
        ),
        color: Colors.grey[100],
      ),
      child: Text(text, style: const TextStyle(fontWeight: FontWeight.bold)),
    );
  }
}

关键注意事项

  • 统一行高:必须保证所有行(表头和内容行)的高度一致,否则垂直滚动时会出现错位问题
  • 滚动同步:如果左侧/右侧列需要单独滚动,要为它们各自创建ScrollController,并在addListener中同步彼此的offset
  • 性能优化:如果数据量极大,建议用ListView.builder替代List.generate,避免一次性创建大量Widget
  • 自适应宽度:示例中固定了左右列的宽度,实际开发中可以根据需求调整,或者使用LayoutBuilder动态计算宽度

内容的提问来源于stack exchange,提问作者kk-at-cbx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:53:16