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

求具备分页、粘性表头、页脚求和功能的Flutter表格组件

实现满足分页、粘性表头和页脚求和的Flutter表格组件

可以通过组合Flutter原生组件实现完全符合需求的表格,解决第三方包的页脚问题和ListView对齐问题,以下是具体实现方案:

核心实现思路

  • 用SingleChildScrollView实现横向滚动,适配多列场景
  • 用CustomScrollView + SliverPersistentHeader实现粘性表头,确保纵向滚动时表头固定
  • 自定义分页逻辑管理当前页数据,实现分页支持
  • 在表格底部添加固定页脚,实时计算当前页数据求和

完整代码示例

import 'package:flutter/material.dart';

// 模拟数据模型
class TableData {
  final String col1;
  final String col2;
  final double col3;
  final double col4;
  // 扩展更多列...

  TableData({
    required this.col1,
    required this.col2,
    required this.col3,
    required this.col4,
  });
}

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

  @override
  State<CustomPaginatedTable> createState() => _CustomPaginatedTableState();
}

class _CustomPaginatedTableState extends State<CustomPaginatedTable> {
  // 分页配置
  final int _rowsPerPage = 10;
  int _currentPage = 0;
  late List<TableData> _allData;
  late List<TableData> _currentPageData;

  // 计算当前页求和值
  double get _currentPageSum {
    return _currentPageData.fold(0, (sum, item) => sum + item.col3 + item.col4);
  }

  @override
  void initState() {
    super.initState();
    // 生成模拟数据
    _allData = List.generate(100, (index) => TableData(
      col1: "数据${index+1}",
      col2: "分类${index%5}",
      col3: (index+1)*10.0,
      col4: (index+1)*5.0,
    ));
    _updateCurrentPageData();
  }

  // 更新当前页数据
  void _updateCurrentPageData() {
    final startIndex = _currentPage * _rowsPerPage;
    final endIndex = startIndex + _rowsPerPage;
    _currentPageData = _allData.sublist(
      startIndex,
      endIndex > _allData.length ? _allData.length : endIndex,
    );
  }

  // 切换页码
  void _changePage(int newPage) {
    setState(() {
      _currentPage = newPage;
      _updateCurrentPageData();
    });
  }

  // 粘性表头代理
  class _HeaderDelegate extends SliverPersistentHeaderDelegate {
    @override
    Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
      return Container(
        color: Colors.grey[50],
        child: _buildTableRow([
          const Text("列1"),
          const Text("列2"),
          const Text("数值列1"),
          const Text("数值列2"),
          // 添加更多列头...
        ], isHeader: true),
      );
    }

    @override
    double get maxExtent => 56;

    @override
    double get minExtent => 56;

    @override
    bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => false;
  }

  // 构建表格行
  Widget _buildTableRow(List<Widget> cells, {bool isHeader = false}) {
    return Row(
      children: cells.map((cell) {
        return Expanded(
          flex: 1,
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
            decoration: BoxDecoration(
              border: Border(
                bottom: BorderSide(color: Colors.grey[200]!),
                right: BorderSide(color: Colors.grey[200]!),
              ),
            ),
            child: DefaultTextStyle(
              style: TextStyle(
                fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
                color: isHeader ? Colors.black87 : Colors.black54,
              ),
              child: cell,
            ),
          ),
        );
      }).toList(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: SizedBox(
          width: MediaQuery.of(context).size.width * 2, // 适配多列宽度,可根据实际列数调整
          child: Column(
            children: [
              Expanded(
                child: CustomScrollView(
                  slivers: [
                    // 粘性表头
                    SliverPersistentHeader(
                      pinned: true,
                      delegate: _HeaderDelegate(),
                    ),
                    // 数据行
                    SliverList(
                      delegate: SliverChildBuilderDelegate(
                        (context, index) {
                          final data = _currentPageData[index];
                          return _buildTableRow([
                            Text(data.col1),
                            Text(data.col2),
                            Text(data.col3.toStringAsFixed(2)),
                            Text(data.col4.toStringAsFixed(2)),
                            // 对应更多列...
                          ]);
                        },
                        childCount: _currentPageData.length,
                      ),
                    ),
                  ],
                ),
              ),
              // 页脚求和行
              Container(
                color: Colors.grey[100],
                child: _buildTableRow([
                  const Text("当前页合计", style: TextStyle(fontWeight: FontWeight.bold)),
                  const SizedBox(),
                  Text(_currentPageSum.toStringAsFixed(2), style: const TextStyle(fontWeight: FontWeight.bold)),
                  const SizedBox(),
                  // 对应更多列的空占位...
                ]),
              ),
              // 分页控件
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 16),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    IconButton(
                      onPressed: _currentPage > 0 ? () => _changePage(_currentPage -1) : null,
                      icon: const Icon(Icons.chevron_left),
                    ),
                    Text("第${_currentPage+1}页 / 共${(_allData.length/_rowsPerPage).ceil()}页"),
                    IconButton(
                      onPressed: _currentPage < (_allData.length/_rowsPerPage).ceil()-1 ? () => _changePage(_currentPage +1) : null,
                      icon: const Icon(Icons.chevron_right),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 调用示例
void main() => runApp(const MaterialApp(home: CustomPaginatedTable()));

关键特性说明

  1. 粘性表头:通过SliverPersistentHeader将表头固定在顶部,纵向滚动时不会跟随数据行移出视野
  2. 分页逻辑:手动维护当前页索引和数据切片,可自由调整每页行数,分页控件支持前后页切换
  3. 页脚求和:通过fold方法实时计算当前页指定列的总和,页脚行固定在表格底部
  4. 横向滚动:外层SingleChildScrollView设置横向滚动,配合固定宽度的SizedBox适配多列场景,同时保证表头与数据列对齐
  5. 列对齐:所有行使用相同的Expanded布局,确保表头、数据行、页脚的列宽完全一致,解决ListView对齐问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 13:35:47