在这里插入图片描述

网格布局是组织多个Card的常见方式,能够有效利用屏幕空间,展示大量卡片内容。本文将详细介绍Card在网格布局中的应用,包括基础网格布局、响应式设计、卡片排序、瀑布流布局等。


一、网格布局基础

GridView是Flutter中实现网格布局的核心组件,可以方便地排列多个Card。

1.1 GridView类型对比

GridView

GridView.count

GridView.extent

GridView.builder

GridView.custom

固定列数

crossAxisCount指定

简单场景

固定宽度

maxCrossAxisExtent指定

自适应场景

动态生成

itemBuilder构建

性能优化

完全自定义

自定义代理

高级场景

1.2 不同GridView对比

类型 列数控制 性能 使用场景 难度
GridView.count 固定列数 一般 布局固定 简单
GridView.extent 自适应 一般 卡片宽度固定 简单
GridView.builder 自适应 大量数据 中等
GridView.custom 完全自定义 最好 复杂布局 较难

二、基础网格布局

2.1 GridView.count实现

class BasicCardGrid extends StatelessWidget {
  final int itemCount;

  const BasicCardGrid({super.key, required this.itemCount});

  
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 2, // 两列
      crossAxisSpacing: 16, // 水平间距
      mainAxisSpacing: 16, // 垂直间距
      padding: const EdgeInsets.all(16),
      children: List.generate(
        itemCount,
        (index) => Card(
          child: Column(
            children: [
              Container(
                height: 100,
                color: Colors.primaries[index % Colors.primaries.length],
                child: Center(
                  child: Text(
                    '${index + 1}',
                    style: const TextStyle(
                      fontSize: 32,
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(12),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '卡片 ${index + 1}',
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '这是第${index + 1}个卡片的描述',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey.shade600,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2.2 GridView.extent实现

class FixedWidthCardGrid extends StatelessWidget {
  const FixedWidthCardGrid({super.key});

  
  Widget build(BuildContext context) {
    return GridView.extent(
      maxCrossAxisExtent: 200, // 卡片最大宽度
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      padding: const EdgeInsets.all(16),
      childAspectRatio: 0.75, // 宽高比
      children: List.generate(
        20,
        (index) => Card(
          child: Column(
            children: [
              Container(
                height: 120,
                color: Colors.teal.shade100,
                child: const Center(
                  child: Icon(
                    Icons.image,
                    size: 48,
                    color: Colors.teal,
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(12),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '图片卡片 ${index + 1}',
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '固定宽度网格',
                      style: TextStyle(
                        fontSize: 11,
                        color: Colors.grey.shade600,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

2.3 GridView.builder实现

class EfficientCardGrid extends StatelessWidget {
  const EfficientCardGrid({super.key});

  
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 16,
        mainAxisSpacing: 16,
        childAspectRatio: 0.8,
      ),
      padding: const EdgeInsets.all(16),
      itemCount: 100, // 大量数据
      itemBuilder: (context, index) {
        return Card(
          child: Column(
            children: [
              Container(
                height: 100,
                color: Colors.blue.shade100,
                child: Center(
                  child: Text(
                    '${index + 1}',
                    style: const TextStyle(
                      fontSize: 28,
                      color: Colors.blue,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(12),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '卡片 ${index + 1}',
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 14,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '性能优化网格',
                      style: TextStyle(
                        fontSize: 11,
                        color: Colors.grey.shade600,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

三、响应式网格布局

3.1 根据屏幕宽度调整列数

屏幕宽度

小屏 手机

中屏 平板竖屏

大屏 平板横屏

超大屏 桌面

2列

3列

4列

5列

class ResponsiveCardGrid extends StatelessWidget {
  const ResponsiveCardGrid({super.key});

  
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // 根据屏幕宽度决定列数
        int crossAxisCount;
        if (constraints.maxWidth < 600) {
          crossAxisCount = 2; // 手机
        } else if (constraints.maxWidth < 900) {
          crossAxisCount = 3; // 小平板
        } else if (constraints.maxWidth < 1200) {
          crossAxisCount = 4; // 大平板
        } else {
          crossAxisCount = 5; // 桌面
        }

        return GridView.count(
          crossAxisCount: crossAxisCount,
          crossAxisSpacing: 16,
          mainAxisSpacing: 16,
          padding: const EdgeInsets.all(16),
          children: List.generate(
            20,
            (index) => Card(
              child: Column(
                children: [
                  Container(
                    height: 80,
                    color: Colors.primaries[index % Colors.primaries.length],
                    child: Center(
                      child: Text(
                        '${index + 1}',
                        style: const TextStyle(
                          fontSize: 24,
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(12),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          '响应式卡片 ${index + 1}',
                          style: const TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 14,
                          ),
                        ),
                        Text(
                          '当前列数: $crossAxisCount',
                          style: TextStyle(
                            fontSize: 11,
                            color: Colors.grey.shade600,
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}

3.2 设备类型检测

class AdaptiveCardGrid extends StatelessWidget {
  const AdaptiveCardGrid({super.key});

  
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: _getMaxCrossAxisExtent(context),
        mainAxisSpacing: 16,
        crossAxisSpacing: 16,
        childAspectRatio: _getChildAspectRatio(context),
      ),
      padding: const EdgeInsets.all(16),
      itemCount: 30,
      itemBuilder: (context, index) => _buildCard(index, context),
    );
  }

  double _getMaxCrossAxisExtent(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return 180;
    if (width < 900) return 220;
    if (width < 1200) return 260;
    return 300;
  }

  double _getChildAspectRatio(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return 0.8;
    if (width < 900) return 0.85;
    if (width < 1200) return 0.9;
    return 0.95;
  }

  Widget _buildCard(int index, BuildContext context) {
    return Card(
      child: Column(
        children: [
          Container(
            height: MediaQuery.of(context).size.width < 600 ? 80 : 100,
            color: Colors.orange.shade100,
            child: const Center(
              child: Icon(
                Icons.card_giftcard,
                size: 40,
                color: Colors.orange,
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '自适应卡片 ${index + 1}',
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 14,
                  ),
                ),
                Text(
                  '根据设备自动调整',
                  style: TextStyle(
                    fontSize: 11,
                    color: Colors.grey.shade600,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

四、瀑布流布局

4.1 使用Flutter Masonry包

dependencies:
  flutter_staggered_grid_view: ^0.6.2
class MasonryCardGrid extends StatelessWidget {
  const MasonryCardGrid({super.key});

  
  Widget build(BuildContext context) {
    return MasonryGridView.count(
      crossAxisCount: 2,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      padding: const EdgeInsets.all(16),
      itemCount: 15,
      itemBuilder: (context, index) {
        final height = 100.0 + (index * 20) % 150; // 随机高度
        return Card(
          child: Container(
            height: height,
            color: Colors.primaries[index % Colors.primaries.length].shade100,
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '卡片 ${index + 1}',
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 16,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  '高度: ${height.toInt()}',
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey.shade700,
                  ),
                ),
                const Spacer(),
                Text(
                  '瀑布流布局\n不同高度的卡片',
                  style: TextStyle(
                    fontSize: 11,
                    color: Colors.grey.shade600,
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

4.2 自定义瀑布流

class CustomMasonryLayout extends StatelessWidget {
  const CustomMasonryLayout({super.key});

  
  Widget build(BuildContext context) {
    final cards = List.generate(20, (index) => _CardData(
      title: '卡片 ${index + 1}',
      content: '这是卡片 ${index + 1} 的详细内容',
      height: 100 + (index % 5) * 30,
      color: Colors.primaries[index % Colors.primaries.length],
    ));

    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Column(
              children: cards
                  .where((card) => cards.indexOf(card) % 2 == 0)
                  .map((card) => _buildMasonryCard(card))
                  .toList(),
            ),
          ),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              children: cards
                  .where((card) => cards.indexOf(card) % 2 == 1)
                  .map((card) => _buildMasonryCard(card))
                  .toList(),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildMasonryCard(_CardData card) {
    return Card(
      margin: const EdgeInsets.only(bottom: 16),
      child: Container(
        height: card.height.toDouble(),
        decoration: BoxDecoration(
          color: card.color.shade100,
          borderRadius: BorderRadius.circular(8),
        ),
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              card.title,
              style: const TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              card.content,
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey.shade700,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _CardData {
  final String title;
  final String content;
  final int height;
  final Color color;

  _CardData({
    required this.title,
    required this.content,
    required this.height,
    required this.color,
  });
}

五、卡片排序与过滤

5.1 排序功能

class SortableCardGrid extends StatefulWidget {
  const SortableCardGrid({super.key});

  
  State<SortableCardGrid> createState() => _SortableCardGridState();
}

class _SortableCardGridState extends State<SortableCardGrid> {
  int _sortType = 0;
  final List<_SortCardData> _cards = List.generate(
    20,
    (index) => _SortCardData(
      id: index + 1,
      title: '卡片 ${index + 1}',
      value: (index + 1) * 10,
      date: DateTime.now().subtract(Duration(days: index)),
    ),
  );

  List<_SortCardData> get _sortedCards {
    switch (_sortType) {
      case 0:
        return _cards; // 默认
      case 1:
        return List.from(_cards)..sort((a, b) => a.title.compareTo(b.title));
      case 2:
        return List.from(_cards)..sort((a, b) => a.value.compareTo(b.value));
      case 3:
        return List.from(_cards)..sort((a, b) => b.value.compareTo(a.value));
      default:
        return _cards;
    }
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildSortOptions(),
        Expanded(
          child: GridView.count(
            crossAxisCount: 2,
            crossAxisSpacing: 16,
            mainAxisSpacing: 16,
            padding: const EdgeInsets.all(16),
            children: _sortedCards
                .map((card) => Card(
                      child: Padding(
                        padding: const EdgeInsets.all(12),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              card.title,
                              style: const TextStyle(
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            Text('值: ${card.value}'),
                            Text('日期: ${card.date.toString().substring(0, 10)}'),
                          ],
                        ),
                      ),
                    ))
                .toList(),
          ),
        ),
      ],
    );
  }

  Widget _buildSortOptions() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Wrap(
        spacing: 8,
        children: [
          ChoiceChip(
            label: const Text('默认'),
            selected: _sortType == 0,
            onSelected: (selected) {
              setState(() {
                _sortType = 0;
              });
            },
          ),
          ChoiceChip(
            label: const Text('按名称'),
            selected: _sortType == 1,
            onSelected: (selected) {
              setState(() {
                _sortType = 1;
              });
            },
          ),
          ChoiceChip(
            label: const Text('值升序'),
            selected: _sortType == 2,
            onSelected: (selected) {
              setState(() {
                _sortType = 2;
              });
            },
          ),
          ChoiceChip(
            label: const Text('值降序'),
            selected: _sortType == 3,
            onSelected: (selected) {
              setState(() {
                _sortType = 3;
              });
            },
          ),
        ],
      ),
    );
  }
}

class _SortCardData {
  final int id;
  final String title;
  final int value;
  final DateTime date;

  _SortCardData({
    required this.id,
    required this.title,
    required this.value,
    required this.date,
  });
}

5.2 过滤功能

class FilterableCardGrid extends StatefulWidget {
  const FilterableCardGrid({super.key});

  
  State<FilterableCardGrid> createState() => _FilterableCardGridState();
}

class _FilterableCardGridState extends State<FilterableCardGrid> {
  final List<String> _categories = ['全部', '红色', '蓝色', '绿色', '黄色'];
  String _selectedCategory = '全部';

  final List<_FilterCardData> _cards = [
    _FilterCardData(title: '红色卡片1', category: '红色', value: 10),
    _FilterCardData(title: '蓝色卡片1', category: '蓝色', value: 20),
    _FilterCardData(title: '绿色卡片1', category: '绿色', value: 30),
    _FilterCardData(title: '黄色卡片1', category: '黄色', value: 40),
    _FilterCardData(title: '红色卡片2', category: '红色', value: 15),
    _FilterCardData(title: '蓝色卡片2', category: '蓝色', value: 25),
    _FilterCardData(title: '绿色卡片2', category: '绿色', value: 35),
    _FilterCardData(title: '黄色卡片2', category: '黄色', value: 45),
  ];

  List<_FilterCardData> get _filteredCards {
    if (_selectedCategory == '全部') {
      return _cards;
    }
    return _cards.where((card) => card.category == _selectedCategory).toList();
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildFilterOptions(),
        Expanded(
          child: GridView.count(
            crossAxisCount: 2,
            crossAxisSpacing: 16,
            mainAxisSpacing: 16,
            padding: const EdgeInsets.all(16),
            children: _filteredCards
                .map((card) => Card(
                      color: _getCategoryColor(card.category).shade100,
                      child: Padding(
                        padding: const EdgeInsets.all(12),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              card.title,
                              style: const TextStyle(
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            Text('分类: ${card.category}'),
                            Text('值: ${card.value}'),
                          ],
                        ),
                      ),
                    ))
                .toList(),
          ),
        ),
      ],
    );
  }

  Widget _buildFilterOptions() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Wrap(
          spacing: 8,
          children: _categories.map((category) {
            return FilterChip(
              label: Text(category),
              selected: _selectedCategory == category,
              onSelected: (selected) {
                setState(() {
                  _selectedCategory = category;
                });
              },
              selectedColor: _getCategoryColor(category).shade200,
            );
          }).toList(),
        ),
      ),
    );
  }

  Color _getCategoryColor(String category) {
    switch (category) {
      case '红色':
        return Colors.red;
      case '蓝色':
        return Colors.blue;
      case '绿色':
        return Colors.green;
      case '黄色':
        return Colors.yellow;
      default:
        return Colors.grey;
    }
  }
}

class _FilterCardData {
  final String title;
  final String category;
  final int value;

  _FilterCardData({
    required this.title,
    required this.category,
    required this.value,
  });
}

六、卡片拖拽排序

6.1 Reorderable卡片

dependencies:
  reorderable_grid_view: ^2.2.7
class ReorderableCardGrid extends StatefulWidget {
  const ReorderableCardGrid({super.key});

  
  State<ReorderableCardGrid> createState() => _ReorderableCardGridState();
}

class _ReorderableCardGridState extends State<ReorderableCardGrid> {
  final List<_ReorderCardData> _cards = List.generate(
    12,
    (index) => _ReorderCardData(
      id: index,
      title: '卡片 ${index + 1}',
      color: Colors.primaries[index % Colors.primaries.length],
    ),
  );

  
  Widget build(BuildContext context) {
    return ReorderableGridView.count(
      crossAxisCount: 2,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      padding: const EdgeInsets.all(16),
      children: _cards
          .map((card) => Card(
                key: ValueKey(card.id),
                color: card.color.shade100,
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    children: [
                      Icon(
                        Icons.drag_handle,
                        color: Colors.grey.shade600,
                      ),
                      const SizedBox(height: 12),
                      Text(
                        card.title,
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        '长按拖拽',
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ],
                  ),
                ),
              ))
          .toList(),
      onReorder: (oldIndex, newIndex) {
        setState(() {
          if (newIndex > oldIndex) {
            newIndex -= 1;
          }
          final item = _cards.removeAt(oldIndex);
          _cards.insert(newIndex, item);
        });
      },
    );
  }
}

class _ReorderCardData {
  final int id;
  final String title;
  final Color color;

  _ReorderCardData({
    required this.id,
    required this.title,
    required this.color,
  });
}

七、完整网格布局示例

class CardGridLayoutExample extends StatelessWidget {
  const CardGridLayoutExample({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Card网格布局'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: DefaultTabController(
        length: 5,
        child: Column(
          children: [
            TabBar(
              tabs: const [
                Tab(text: '基础网格'),
                Tab(text: '响应式'),
                Tab(text: '瀑布流'),
                Tab(text: '排序'),
                Tab(text: '过滤'),
              ],
            ),
            Expanded(
              child: TabBarView(
                children: [
                  const BasicCardGrid(itemCount: 20),
                  const ResponsiveCardGrid(),
                  const MasonryCardGrid(),
                  const SortableCardGrid(),
                  const FilterableCardGrid(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

八、网格布局最佳实践

性能优化建议

优化项 说明 效果
使用builder 避免一次性创建所有Widget 内存优化
添加缓存 保持列表位置状态 用户体验
懒加载图片 按需加载图片资源 性能提升
使用const 减少重建开销 渲染优化
虚拟滚动 只渲染可见项 性能大幅提升

布局设计原则

// ✅ 好的做法
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemCount: 1000,
  itemBuilder: (context, index) {
    return const Card(
      child: Text('卡片'),
    );
  },
)

// ❌ 避免的做法
GridView.count(
  crossAxisCount: 2,
  children: List.generate(
    1000,
    (index) => Card(
      child: Text('卡片 $index'),
    ),
  ),
)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐