Flutter实现固定表头+可滚动内容表格(不使用第三方库)
Flutter实现固定表头、内容自适应滚动的Table(无第三方库)
问题分析
用Expanded包裹导致卡顿,本质是未对内容Table的高度做有效约束,同时大量直接生成的TableRow会让Widget树过于庞大,增加布局计算压力;硬编码高度又无法适配不同屏幕尺寸。
解决方案
通过调整Widget结构,让内容滚动区域自适应剩余空间,并优化列表渲染性能:
1. 核心结构调整
将第二个Obx内的Column保留,给内容区域的SingleChildScrollView套上Expanded,让它自动占据父Column的剩余可用空间,同时确保表头和内容Table的列宽完全一致,避免错位:
Obx(() { return Column( children: [ // 固定表头Table Table( columnWidths: const { 0: FixedColumnWidth(100), 1: FlexColumnWidth(2), 2: FlexColumnWidth(1), }, border: TableBorder.all(), children: const [ TableRow( children: [ Padding(padding: EdgeInsets.all(8), child: Text('ID')), Padding(padding: EdgeInsets.all(8), child: Text('名称')), Padding(padding: EdgeInsets.all(8), child: Text('状态')), ], ), ], ), const SizedBox(height: 8), // 自适应高度的可滚动内容区域 Expanded( child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Table( // 必须和表头保持完全一致的列宽配置 columnWidths: const { 0: FixedColumnWidth(100), 1: FlexColumnWidth(2), 2: FlexColumnWidth(1), }, border: TableBorder.all(), children: List.generate(50, (index) { return TableRow( children: [ Padding(padding: EdgeInsets.all(8), child: Text('$index')), Padding(padding: EdgeInsets.all(8), child: Text('测试数据$index')), Padding(padding: EdgeInsets.all(8), child: Text(index % 2 == 0 ? '正常' : '异常')), ], ); }), ), ), ), ], ); })
2. 解决卡顿的关键优化
如果内容数据量较大(比如超过50行),直接生成所有TableRow会导致Widget树节点过多,引发卡顿。此时可以用ListView.builder模拟Table结构,实现懒加载渲染:
// 表头部分保持不变 Table(/* 表头配置 */), const SizedBox(height: 8), // 优化后的可滚动内容区域 Expanded( child: ListView.builder( itemCount: 1000, // 大量数据 itemBuilder: (context, index) { return Row( children: [ // 对应表头第一列,宽度一致 Container( width: 100, padding: const EdgeInsets.all(8), decoration: const BoxDecoration( border: Border(bottom: BorderSide(), right: BorderSide()), ), child: Text('$index'), ), // 对应表头第二列,flex比例一致 Expanded( flex: 2, child: Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( border: Border(bottom: BorderSide(), right: BorderSide()), ), child: Text('测试数据$index'), ), ), // 对应表头第三列,flex比例一致 Expanded( flex: 1, child: Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( border: Border(bottom: BorderSide()), ), child: Text(index % 2 == 0 ? '正常' : '异常'), ), ), ], ); }, ), )
关键注意事项
- 列宽对齐:表头和内容的列宽(或flex比例)必须严格匹配,否则会出现视觉错位。
- 自适应高度:
Expanded必须放在Column内,且Column的父容器要有确定的高度约束(比如Scaffold的body本身是有高度约束的)。 - 性能优化:数据量大时,优先用
ListView.builder懒加载,避免一次性生成大量Widget。
内容的提问来源于stack exchange,提问作者GnRSlashSP
相关产品推荐
相关产品推荐

