什么是 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

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

更多推荐