求具备分页、粘性表头、页脚求和功能的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()));
关键特性说明
- 粘性表头:通过
SliverPersistentHeader将表头固定在顶部,纵向滚动时不会跟随数据行移出视野 - 分页逻辑:手动维护当前页索引和数据切片,可自由调整每页行数,分页控件支持前后页切换
- 页脚求和:通过
fold方法实时计算当前页指定列的总和,页脚行固定在表格底部 - 横向滚动:外层
SingleChildScrollView设置横向滚动,配合固定宽度的SizedBox适配多列场景,同时保证表头与数据列对齐 - 列对齐:所有行使用相同的
Expanded布局,确保表头、数据行、页脚的列宽完全一致,解决ListView对齐问题
内容的提问来源于stack exchange,提问作者bahadir arslan
相关产品推荐
相关产品推荐

