当你的内容超出一个屏幕的高度时,你就需要一个可滚动的容器。ListView
和 GridView
是 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。它接收context
和index
(当前项的索引)作为参数。
- 适用场景:几乎所有长列表或数据量不确定的列表,如新闻 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
如何排列其子项。最常用的有两个:
-
SliverGridDelegateWithFixedCrossAxisCount
:- 创建一个在交叉轴 上具有固定数量的网格。
- 例如,在垂直滚动的
GridView
中,交叉轴是水平的,crossAxisCount: 3
就意味着每行固定有 3 个子项。
-
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),
),
],
),
);
},
),
),
);
}
}
总结:如何选择?
-
确定数据量:
- 数据量小且固定? -> 可以使用
ListView()
或GridView.count()
的默认构造函数。 - 数据量大或不确定? -> 必须使用
ListView.builder
或GridView.builder
以保证性能。
- 数据量小且固定? -> 可以使用
-
确定布局样式:
- 单列垂直/水平滚动列表? ->
ListView
。 - 需要分隔符? ->
ListView.separated
。 - 多行多列的网格布局? ->
GridView
。
- 单列垂直/水平滚动列表? ->
-
确定网格列数:
- 固定列数? ->
GridView.count
或GridView.builder
+SliverGridDelegateWithFixedCrossAxisCount
。 - 希望根据屏幕宽度自适应列数? ->
GridView.builder
+SliverGridDelegateWithMaxCrossAxisExtent
。
- 固定列数? ->
掌握了 ListView
和 GridView
的高效用法,你就具备了构建流畅、可响应的数据密集型应用的核心能力。接下来,我们将进入 Flutter 开发中另一个至关重要的主题:页面导航与路由管理。我们下篇见!