如何在Flutter中实现与GeForce Experience同款固定项尺寸的响应式GridView.builder
Flutter实现类似GeForce Experience的响应式流式布局
Flutter原生GridView.builder无法直接实现「固定基础尺寸+剩余宽度自动填充」的布局效果,以下提供两种方案分别适配小数据量和大数据量场景:
方案1:基于Wrap的实现(适合数据量较小场景)
这种方式简单直观,通过LayoutBuilder获取容器可用宽度,动态计算项的宽度,让最后一行的项自动填充剩余空间:
class ResponsiveStretchWrap extends StatelessWidget { final double itemBaseWidth; // 项的基础固定宽度 final List<Widget> items; const ResponsiveStretchWrap({ super.key, required this.itemBaseWidth, required this.items, }); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final availableWidth = constraints.maxWidth; final crossAxisCount = (availableWidth / itemBaseWidth).floor(); if (crossAxisCount == 0) return const SizedBox.shrink(); // 拆分数据为多行 List<List<Widget>> rows = []; for (int i = 0; i < items.length; i += crossAxisCount) { rows.add(items.sublist(i, i + crossAxisCount > items.length ? items.length : i + crossAxisCount)); } return Column( children: rows.map((rowItems) { final isLastRow = rows.last == rowItems; final itemCount = rowItems.length; // 最后一行项数不足时,拉伸填充剩余宽度 final itemWidth = isLastRow && itemCount < crossAxisCount ? availableWidth / itemCount : itemBaseWidth; return Row( children: rowItems.map((item) { return SizedBox( width: itemWidth, child: item, ); }).toList(), ); }).toList(), ); }, ); } }
使用示例:
ResponsiveStretchWrap( itemBaseWidth: 200, items: List.generate(10, (index) { return Container( margin: const EdgeInsets.all(4), color: Colors.blue[100 * (index % 9)], height: 150, child: Center(child: Text('Item $index')), ); }), )
方案2:支持懒加载的Builder实现(适合大量数据场景)
如果需要处理大量数据,可结合ListView.builder实现懒加载,仅渲染当前可见的行:
class ResponsiveStretchGridBuilder extends StatelessWidget { final double itemBaseWidth; final int itemCount; final Widget Function(BuildContext, int) itemBuilder; const ResponsiveStretchGridBuilder({ super.key, required this.itemBaseWidth, required this.itemCount, required this.itemBuilder, }); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final availableWidth = constraints.maxWidth; final crossAxisCount = (availableWidth / itemBaseWidth).floor(); if (crossAxisCount == 0) return const SizedBox.shrink(); final rowCount = (itemCount / crossAxisCount).ceil(); return ListView.builder( itemCount: rowCount, itemBuilder: (context, rowIndex) { final startIndex = rowIndex * crossAxisCount; final endIndex = startIndex + crossAxisCount > itemCount ? itemCount : startIndex + crossAxisCount; final rowItemCount = endIndex - startIndex; final isLastRow = rowIndex == rowCount - 1; final itemWidth = isLastRow && rowItemCount < crossAxisCount ? availableWidth / rowItemCount : itemBaseWidth; return Row( children: List.generate(rowItemCount, (colIndex) { final itemIndex = startIndex + colIndex; return SizedBox( width: itemWidth, child: itemBuilder(context, itemIndex), ); }), ); }, ); }, ); } }
使用示例:
ResponsiveStretchGridBuilder( itemBaseWidth: 200, itemCount: 100, // 支持百级以上数据量 itemBuilder: (context, index) { return Container( margin: const EdgeInsets.all(4), color: Colors.blue[100 * (index % 9)], height: 150, child: Center(child: Text('Item $index')), ); }, )
核心逻辑说明
- 动态列数计算:通过
LayoutBuilder获取容器宽度,自动计算每行能容纳的基础尺寸项数量 - 最后一行适配:当最后一行项数不足时,将剩余宽度平均分配给该行所有项,实现填充效果
- 性能优化:方案2通过
ListView.builder实现懒加载,避免一次性加载大量数据导致的内存占用过高问题
内容的提问来源于stack exchange,提问作者Anas-Qasem
相关产品推荐
相关产品推荐

