Flutter 系列教程:列表与网格 - `ListView` 和 `GridView`

当你的内容超出一个屏幕的高度时,你就需要一个可滚动的容器。ListViewGridView 是 Flutter 中最重要、最常用的可滚动组件,它们专门用于高效地展示大量数据。理解它们的不同构造方式和适用场景对于构建高性能的应用至关-重要。

学习目标

  • 理解 ListView 最常用的三种构造函数:默认构造函数、.builder().separated(),并了解它们各自的性能特点和适用场景。
  • 掌握 GridView 的核心概念 SliverGridDelegate,并学会使用 GridView.count()GridView.builder() 来创建网格布局。
  • 学会根据具体需求选择最合适的列表或网格实现方式。

1. ListView:线性可滚动列表

ListView 是一个将子组件沿垂直(默认)或水平方向线性排列的可滚动组件。

方式一:默认构造函数 ListView()

这是最简单的方式,它接收一个 List<Widget> 作为 children

  • 特点 :一次性创建并渲染列表中的所有子组件。
  • 适用场景 :当列表项数量很少且固定时。例如,一个设置页面,里面只有十几个固定的选项。
  • 性能陷阱绝对不要用这种方式来展示一个很长或无限的列表!因为它会一次性把所有 Widget 都加载到内存中,即使它们在屏幕外,这会导致严重的性能问题和内存占用。
dart 复制代码
ListView(
  padding: const EdgeInsets.all(8),
  children: <Widget>[
    Container(height: 50, color: Colors.amber[600], child: const Center(child: Text('Entry A'))),
    Container(height: 50, color: Colors.amber[500], child: const Center(child: Text('Entry B'))),
    Container(height: 50, color: Colors.amber[100], child: const Center(child: Text('Entry C'))),
  ],
)

方式二:ListView.builder()【最常用、性能最好】

这是构建长列表的标准和推荐 方式。它采用"懒加载"机制,只创建和渲染那些当前在屏幕上可见的列表项。

  • 特点:按需构建,性能极高。
  • 核心属性
    • itemCount: 列表项的总数。
    • itemBuilder: 一个函数,用于构建每个列表项的 Widget。它接收 contextindex (当前项的索引)作为参数。
  • 适用场景:几乎所有长列表或数据量不确定的列表,如新闻 Feed、聊天记录、商品列表等。

代码示例:构建一个动态新闻列表

less 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    // 假设这是从 API 获取的数据
    final List<String> entries = List<String>.generate(50, (i) => 'News Article ${i + 1}');

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('ListView.builder Demo')),
        body: ListView.builder(
          itemCount: entries.length, // 1. 告诉 ListView 总共有多少项
          itemBuilder: (BuildContext context, int index) { // 2. 按需构建每一项
            return ListTile(
              leading: CircleAvatar(child: Text('${index + 1}')),
              title: Text(entries[index]),
              subtitle: Text('This is the subtitle for item $index'),
              onTap: () {
                print('Tapped on ${entries[index]}');
              },
            );
          },
        ),
      ),
    );
  }
}

方式三:ListView.separated()

这个构造函数与 .builder() 非常相似,但增加了一个额外的 separatorBuilder 函数,用于在每个列表项之间构建一个分隔符 Widget。

  • 特点 :在 .builder() 的基础上,可以方便地添加自定义的分隔线。
  • 适用场景:需要明确分隔符的列表,如联系人列表、设置菜单等。
dart 复制代码
ListView.separated(
  itemCount: 25,
  separatorBuilder: (BuildContext context, int index) => const Divider(color: Colors.grey), // 分隔符构建器
  itemBuilder: (BuildContext context, int index) {
    return ListTile(
      title: Text('Item $index'),
    );
  },
)

2. GridView:二维可滚动网格

GridView 用于在二维空间中排列子组件,常用于相册、商品分类等场景。

核心概念:gridDelegate

GridView 的布局方式由 gridDelegate 属性控制,它接收一个 SliverGridDelegate 对象。这个 "delegate" (委托) 告诉 GridView 如何排列其子项。最常用的有两个:

  1. SliverGridDelegateWithFixedCrossAxisCount:

    • 创建一个在交叉轴 上具有固定数量的网格。
    • 例如,在垂直滚动的 GridView 中,交叉轴是水平的,crossAxisCount: 3 就意味着每行固定有 3 个子项。
  2. SliverGridDelegateWithMaxCrossAxisExtent:

    • 创建一个子项在交叉轴 上具有最大宽度/高度的网格。
    • 例如,maxCrossAxisExtent: 150 意味着每个子项的宽度最多为 150 像素。GridView 会根据屏幕总宽度自动计算一行能放几个子项。这对于构建响应式布局非常有用。

方式一:GridView.count()

这是 SliverGridDelegateWithFixedCrossAxisCount 的一个便捷构造函数。

  • 特点:简单直观,快速创建一个固定列数的网格。
  • 适用场景:当你明确知道每行需要显示几个项目时。
dart 复制代码
GridView.count(
  crossAxisCount: 3, // 每行3个
  mainAxisSpacing: 10,  // 主轴间距
  crossAxisSpacing: 10, // 交叉轴间距
  children: List.generate(20, (index) {
    return Container(
      color: Colors.teal[100 * (index % 9)],
      child: Center(child: Text('Item $index')),
    );
  }),
)

方式二:GridView.builder()【性能最好】

ListView.builder() 类似,GridView.builder() 也采用懒加载机制,是构建大型网格的首选。

  • 特点 :按需构建,高性能,与 gridDelegate 结合使用,布局灵活。
  • 适用场景:需要展示大量数据的相册、商品目录等。

代码示例:构建一个商品展示网格

less 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('GridView.builder Demo')),
        body: GridView.builder(
          padding: const EdgeInsets.all(10.0),
          // 1. 提供 Grid Delegate
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,    // 每行2个
            crossAxisSpacing: 10, // 水平间距
            mainAxisSpacing: 10,  // 垂直间距
            childAspectRatio: 3 / 2, // 宽高比
          ),
          itemCount: 30, // 2. 网格项总数
          itemBuilder: (BuildContext context, int index) { // 3. 按需构建
            return Container(
              padding: const EdgeInsets.all(8),
              color: Colors.blueGrey,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.shopping_bag, size: 40, color: Colors.white),
                  Text(
                    'Product $index',
                    style: const TextStyle(color: Colors.white, fontSize: 18),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

总结:如何选择?

  1. 确定数据量

    • 数据量小且固定? -> 可以使用 ListView()GridView.count() 的默认构造函数。
    • 数据量大或不确定? -> 必须使用 ListView.builderGridView.builder 以保证性能。
  2. 确定布局样式

    • 单列垂直/水平滚动列表? -> ListView
    • 需要分隔符? -> ListView.separated
    • 多行多列的网格布局? -> GridView
  3. 确定网格列数

    • 固定列数? -> GridView.countGridView.builder + SliverGridDelegateWithFixedCrossAxisCount
    • 希望根据屏幕宽度自适应列数? -> GridView.builder + SliverGridDelegateWithMaxCrossAxisExtent

掌握了 ListViewGridView 的高效用法,你就具备了构建流畅、可响应的数据密集型应用的核心能力。接下来,我们将进入 Flutter 开发中另一个至关重要的主题:页面导航与路由管理。我们下篇见!

相关推荐
用户352120195603 小时前
React hooks (useRef)
前端
Mintopia4 小时前
⚡当 Next.js 遇上实时通信:Socket.io 与 Pusher 双雄传
前端·后端·全栈
tangdou3690986554 小时前
可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了😱 Node.js System Crashed Because of Logging
前端·javascript·后端
Takklin4 小时前
Vue 与 React 应用初始化机制对比 - 前端框架思考笔记
前端·react.js
Mintopia4 小时前
🎨 数据增强技术在 AIGC 训练中的应用:提升 Web 生成的多样性
前端·javascript·aigc
华仔啊4 小时前
如何用 Vue3 打造高级音乐播放器?进度条+可视化效果,代码简洁可复用!
前端·css·vue.js
小傅哥4 小时前
新项目完结,Ai Agent 智能体、拖拉拽编排!
前端·后端
ttod_qzstudio4 小时前
解决 Vue 3 + TypeScript 中 v-for 循环类型推断问题
前端·vue.js·typescript
一只小风华~4 小时前
Vue Router 的三种历史模式详解
前端·javascript·vue.js·笔记·学习·前端框架·ecmascript