什么是 GridView?

GridView 是 Flutter 中用于显示网格布局的组件,可以创建多列的滚动列表。

用途:

  • 图片墙
  • 商品展示
  • 应用图标网格
  • 相册
  • 任何需要网格布局的场景

GridView 的类型

1. GridView.count - 固定列数

GridView.count(
  crossAxisCount: 3,  // 3 列
  children: List.generate(20, (index) {
    return Container(
      color: Colors.blue,
      child: Center(child: Text('$index')),
    );
  }),
)

2. GridView.extent - 固定最大宽度

GridView.extent(
  maxCrossAxisExtent: 150,  // 每个格子最大宽度 150
  children: List.generate(20, (index) {
    return Container(
      color: Colors.green,
      child: Center(child: Text('$index')),
    );
  }),
)

3. GridView.builder - 动态网格(推荐)

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  itemCount: 20,
  itemBuilder: (context, index) {
    return Container(
      color: Colors.red,
      child: Center(child: Text('$index')),
    );
  },
)

常用属性

1. gridDelegate - 网格代理

// 固定列数
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,           // 列数
    mainAxisSpacing: 10,          // 主轴间距
    crossAxisSpacing: 10,         // 交叉轴间距
    childAspectRatio: 1.0,        // 宽高比
  ),
  itemBuilder: (context, index) {
    return Container(color: Colors.blue);
  },
)

// 固定最大宽度
GridView.builder(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 150,     // 最大宽度
    mainAxisSpacing: 10,
    crossAxisSpacing: 10,
    childAspectRatio: 1.0,
  ),
  itemBuilder: (context, index) {
    return Container(color: Colors.green);
  },
)

2. padding - 内边距

GridView.count(
  crossAxisCount: 3,
  padding: EdgeInsets.all(10),
  children: [...],
)

3. scrollDirection - 滚动方向

// 垂直滚动(默认)
GridView.count(
  scrollDirection: Axis.vertical,
  crossAxisCount: 3,
  children: [...],
)

// 水平滚动
GridView.count(
  scrollDirection: Axis.horizontal,
  crossAxisCount: 3,
  children: [...],
)

实战案例

案例1:图片网格

class ImageGrid extends StatelessWidget {
  final List<String> images = List.generate(
    20,
    (index) => 'https://picsum.photos/200?random=$index',
  );

  
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: EdgeInsets.all(10),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
      ),
      itemCount: images.length,
      itemBuilder: (context, index) {
        return ClipRRect(
          borderRadius: BorderRadius.circular(8),
          child: Image.network(
            images[index],
            fit: BoxFit.cover,
          ),
        );
      },
    );
  }
}

案例2:商品网格

class Product {
  final String name;
  final String image;
  final double price;

  Product({required this.name, required this.image, required this.price});
}

class ProductGrid extends StatelessWidget {
  final List<Product> products = [
    Product(name: 'iPhone 15', image: 'url', price: 7999),
    Product(name: 'MacBook', image: 'url', price: 12999),
    // 更多商品...
  ];

  
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: EdgeInsets.all(10),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
        childAspectRatio: 0.7,
      ),
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return Card(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Expanded(
                child: ClipRRect(
                  borderRadius: BorderRadius.vertical(top: Radius.circular(4)),
                  child: Image.network(
                    product.image,
                    width: double.infinity,
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              Padding(
                padding: EdgeInsets.all(8),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      product.name,
                      style: TextStyle(fontWeight: FontWeight.bold),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                    SizedBox(height: 4),
                    Text(
                      ${product.price}',
                      style: TextStyle(
                        color: Colors.red,
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

案例3:应用图标网格

class AppIconGrid extends StatelessWidget {
  final List<Map<String, dynamic>> apps = [
    {'icon': Icons.camera, 'name': '相机', 'color': Colors.blue},
    {'icon': Icons.photo, 'name': '相册', 'color': Colors.green},
    {'icon': Icons.music_note, 'name': '音乐', 'color': Colors.red},
    {'icon': Icons.video_library, 'name': '视频', 'color': Colors.orange},
    {'icon': Icons.settings, 'name': '设置', 'color': Colors.grey},
    {'icon': Icons.message, 'name': '消息', 'color': Colors.purple},
  ];

  
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: EdgeInsets.all(20),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 4,
        crossAxisSpacing: 15,
        mainAxisSpacing: 15,
      ),
      itemCount: apps.length,
      itemBuilder: (context, index) {
        final app = apps[index];
        return GestureDetector(
          onTap: () {
            print('打开 ${app['name']}');
          },
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                width: 60,
                height: 60,
                decoration: BoxDecoration(
                  color: app['color'],
                  borderRadius: BorderRadius.circular(15),
                ),
                child: Icon(
                  app['icon'],
                  color: Colors.white,
                  size: 30,
                ),
              ),
              SizedBox(height: 8),
              Text(
                app['name'],
                style: TextStyle(fontSize: 12),
              ),
            ],
          ),
        );
      },
    );
  }
}

案例4:瀑布流(不同高度)

import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';

// 需要添加依赖: flutter_staggered_grid_view: ^0.7.0

class StaggeredGrid extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MasonryGridView.count(
      crossAxisCount: 2,
      mainAxisSpacing: 10,
      crossAxisSpacing: 10,
      itemCount: 20,
      itemBuilder: (context, index) {
        return Container(
          height: (index % 3 + 1) * 100.0,
          color: Colors.primaries[index % Colors.primaries.length],
          child: Center(child: Text('$index')),
        );
      },
    );
  }
}

案例5:可选择的网格

class SelectableGrid extends StatefulWidget {
  
  _SelectableGridState createState() => _SelectableGridState();
}

class _SelectableGridState extends State<SelectableGrid> {
  Set<int> selectedItems = {};

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: EdgeInsets.all(10),
          child: Text('已选择: ${selectedItems.length} 项'),
        ),
        Expanded(
          child: GridView.builder(
            padding: EdgeInsets.all(10),
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              crossAxisSpacing: 10,
              mainAxisSpacing: 10,
            ),
            itemCount: 20,
            itemBuilder: (context, index) {
              final isSelected = selectedItems.contains(index);
              return GestureDetector(
                onTap: () {
                  setState(() {
                    if (isSelected) {
                      selectedItems.remove(index);
                    } else {
                      selectedItems.add(index);
                    }
                  });
                },
                child: Container(
                  decoration: BoxDecoration(
                    color: isSelected ? Colors.blue : Colors.grey[300],
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(
                      color: isSelected ? Colors.blue : Colors.transparent,
                      width: 3,
                    ),
                  ),
                  child: Center(
                    child: Text(
                      '$index',
                      style: TextStyle(
                        color: isSelected ? Colors.white : Colors.black,
                        fontSize: 20,
                      ),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

完整示例

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: GridViewDemo(),
    );
  }
}

class GridViewDemo extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('GridView 示例')),
      body: GridView.builder(
        padding: EdgeInsets.all(10),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
        itemCount: 20,
        itemBuilder: (context, index) {
          return Container(
            decoration: BoxDecoration(
              color: Colors.primaries[index % Colors.primaries.length],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Center(
              child: Text(
                '$index',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

常见问题

1. GridView 在 Column 中不显示?

// ❌ 错误
Column(
  children: [
    GridView.count(...),  // 会报错
  ],
)

// ✅ 解决:使用 Expanded
Column(
  children: [
    Expanded(
      child: GridView.count(...),
    ),
  ],
)

// ✅ 或使用 shrinkWrap
Column(
  children: [
    GridView.count(
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
      ...
    ),
  ],
)

2. 如何设置不同的宽高比?

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 0.7,  // 宽:高 = 0.7:1
  ),
  ...
)

3. 如何实现瀑布流?

使用 flutter_staggered_grid_view 包。

属性速查表

属性 说明
gridDelegate 网格代理
itemCount 项目数量
itemBuilder 项目构建器
padding 内边距
scrollDirection 滚动方向
shrinkWrap 是否根据子组件调整大小

SliverGridDelegateWithFixedCrossAxisCount 参数

参数 说明
crossAxisCount 列数
mainAxisSpacing 主轴间距
crossAxisSpacing 交叉轴间距
childAspectRatio 宽高比

总结

GridView 的核心要点:

  1. GridView.count - 固定列数
  2. GridView.extent - 固定最大宽度
  3. GridView.builder - 动态网格(推荐)
  4. 使用 gridDelegate 控制布局
  5. childAspectRatio 控制宽高比

记住:

  • 大量数据用 GridView.builder
  • 在 Column 中使用需要 Expanded 或 shrinkWrap
  • crossAxisCount 设置列数
  • childAspectRatio 设置宽高比

GridView 是展示网格内容的最佳选择!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐