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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:01:44