如何在Flutter与Dart中实现自定义网格布局?
实现自定义交替网格布局(2列小卡片 + 全屏大卡片)
核心思路
每4个2列布局的小卡片后,插入一个全屏宽度的大卡片,所有卡片对应数据库中的不同场馆。我们可以通过**自定义SliverGridDelegate**来实现这种混合跨度的网格布局,无需依赖第三方库。
完整代码实现
首先定义自定义网格代理,处理不同卡片的位置和跨度:
class CustomGridDelegate extends SliverGridDelegate { final int crossAxisCount; final double spacing; // 卡片间距 CustomGridDelegate({required this.crossAxisCount, this.spacing = 4.0}); @override SliverGridLayout getLayout(SliverConstraints constraints) { // 计算单列宽度(减去间距) final crossAxisExtent = (constraints.crossAxisExtent - (crossAxisCount -1)*spacing) / crossAxisCount; // 假设卡片为正方形,可根据需求调整高度 final mainAxisExtent = crossAxisExtent; return _CustomGridLayout( crossAxisCount: crossAxisCount, crossAxisExtent: crossAxisExtent, mainAxisExtent: mainAxisExtent, spacing: spacing, ); } @override bool shouldRelayout(covariant CustomGridDelegate oldDelegate) { return oldDelegate.crossAxisCount != crossAxisCount || oldDelegate.spacing != spacing; } } class _CustomGridLayout extends SliverGridLayout { final int crossAxisCount; final double crossAxisExtent; final double mainAxisExtent; final double spacing; _CustomGridLayout({ required this.crossAxisCount, required this.crossAxisExtent, required this.mainAxisExtent, required this.spacing, }); @override double computeMaxScrollOffset(int childCount) { double totalOffset = 0; int rowInGroup = 0; for (int i = 0; i < childCount; i++) { // 每5个卡片为一组:4个小卡片(2行)+1个大卡片(1行) if ((i + 1) % 5 == 0) { totalOffset += mainAxisExtent + spacing; rowInGroup = 0; } else { if (rowInGroup == 0) { totalOffset += mainAxisExtent + spacing; } rowInGroup = (rowInGroup + 1) % crossAxisCount; } } // 减去最后一个多余的间距 return totalOffset.clamp(0, double.infinity) - (totalOffset >0 ? spacing :0); } @override SliverGridGeometry getGeometryForChildIndex(int index) { int groupIndex = index ~/ 5; int positionInGroup = index % 5; // 每组的基础偏移:2行小卡片 +1行大卡片的高度总和 double mainAxisOffset = groupIndex * (2*(mainAxisExtent + spacing) + mainAxisExtent + spacing); if (positionInGroup ==4) { // 大卡片:跨2列,全屏宽度 return SliverGridGeometry( scrollOffset: mainAxisOffset + 2*(mainAxisExtent + spacing), crossAxisOffset:0, mainAxisExtent: mainAxisExtent, crossAxisExtent: crossAxisExtent*2 + spacing, ); } else { // 小卡片:占1列 int row = positionInGroup ~/ crossAxisCount; int col = positionInGroup % crossAxisCount; mainAxisOffset += row*(mainAxisExtent + spacing); return SliverGridGeometry( scrollOffset: mainAxisOffset, crossAxisOffset: col*(crossAxisExtent + spacing), mainAxisExtent: mainAxisExtent, crossAxisExtent: crossAxisExtent, ); } } @override int getMinChildCount() =>0; }
然后在你的页面中使用这个代理替换原来的网格代理:
if (snapshot.hasData) { List<Venue?> venuesData = snapshot.data!; return Padding( padding: EdgeInsets.only(bottom: 2.h), child: GridView.builder( padding: const EdgeInsets.all(4.0), gridDelegate: const CustomGridDelegate(crossAxisCount:2), itemCount: venuesData.length, itemBuilder: (BuildContext context, int index) { final venue = venuesData[index]!; // 判断是否为全屏卡片,可自定义样式区分 bool isFullWidthCard = (index +1) %5 ==0; return Card( color: isFullWidthCard ? Colors.blue.shade100 : Colors.amber.shade100, child: Center( child: Text( venue.name, textAlign: TextAlign.center, ), ), ); }, ), ); }
关于flutter_staggered_grid_view的重复问题说明
你之前遇到的卡片名称重复,大概率是索引对应错误导致的:比如错误地增加了itemCount(比如写成venuesData.length + venuesData.length~/4),导致部分数据被重复调用。正确用法应该是itemCount直接等于数据源长度,通过staggeredTileBuilder判断每个索引的卡片跨度,代码如下:
// 正确使用flutter_staggered_grid_view的示例 import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; // ... if (snapshot.hasData) { List<Venue?> venuesData = snapshot.data!; return Padding( padding: EdgeInsets.only(bottom: 2.h), child: StaggeredGridView.builder( padding: const EdgeInsets.all(4.0), gridDelegate: SliverStaggeredGridDelegateWithFixedCrossAxisCount( crossAxisCount:2, staggeredTileBuilder: (index) => (index+1)%5==0 ? StaggeredTile.fit(2) : StaggeredTile.fit(1), mainAxisSpacing:4.0, crossAxisSpacing:4.0, ), itemCount: venuesData.length, itemBuilder: (context, index) { final venue = venuesData[index]!; return Card( color: (index+1)%5==0 ? Colors.blue.shade100 : Colors.amber.shade100, child: Center(child: Text(venue.name)), ); }, ), ); }
内容的提问来源于stack exchange,提问作者DiscoQueenXOXO
相关产品推荐
相关产品推荐

