Flutter可滚动组件(4):GridView(网格布局)
在 Flutter 中,GridView
是一个展示数据网格的滚动小部件,类似于表格视图,其中子控件被组织成行和列。它非常适合于展示图像网格、小部件集合等。GridView
同样支持懒加载,这意味着只有当内容进入视口时才会被构建。
一、基本用法
GridView
最基本的用法是包裹一个网格项的集合:
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
children: <Widget>[
Container(color: Colors.red,),
Container(color: Colors.blue,),
// ... 更多的容器
],
)
二、网格委托
GridView
使用GridDelegate
来控制网格的布局属性,如子控件的大小、间距等。Flutter 提供了几种GridDelegate
:
(1)SliverGridDelegateWithFixedCrossAxisCount
这个委托创建一个网格,其交叉轴(列)的数量是固定的:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商
childAspectRatio: 3, // 子元素的宽高比。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度
mainAxisSpacing = 0.0, // 主轴方向子元素的间距
crossAxisSpacing = 0.0, // 横轴方向子元素的间距
)
可以发现,子元素的大小是通过crossAxisCount
和childAspectRatio
两个参数共同决定的。注意,这里的子元素指的是子组件的最大显示空间,注意确保子组件的实际大小不要超出子元素的空间。
下面看一个例子:
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyHomeBody(),
),
);
}
}
class MyHomeBody extends StatelessWidget {
const MyHomeBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 横轴三个子元素
childAspectRatio: 1.0 // 宽高比为1时,子元素的长宽一样
),
children: const <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast)
]);
}
}
效果图如下所示:
(2)SliverGridDelegateWithMaxCrossAxisExten
这个委托创建一个网格,其交叉轴(列)的最大宽度是固定的:
SliverGridDelegateWithMaxCrossAxisExtent({
maxCrossAxisExtent: 450.0, // maxCrossAxisExtent为子元素在横轴上的最大长度
childAspectRatio: 3, // 子元素的宽高比。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度
mainAxisSpacing = 0.0, // 主轴方向子元素的间距
crossAxisSpacing = 0.0, // 横轴方向子元素的间距
})
maxCrossAxisExtent
为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的,举个例子,如果ViewPort
的横轴长度是 450,那么maxCrossAxisExtent
的值在区间 [450/4,450/3] 内。其他参数和SliverGridDelegateWithFixedCrossAxisCount
相同。
下面我们看一个例子:
GridView(
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0 //宽高比为2
),
children: const <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
效果图如下所示:
三、构建方式
3.1 GridView 构建
只需要构建一个 List<Widget>
传入到 children
和构建一个 gridDelegate
即可:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyGridView(),
);
}
}
// 图像数据源
List listData=[
{
"url": 'https://www.itying.com/images/flutter/1.png',
},
{
"url": 'https://www.itying.com/images/flutter/2.png',
},
{
"url": 'https://www.itying.com/images/flutter/3.png',
},
{
"url": 'https://www.itying.com/images/flutter/4.png',
},
{
"url": 'https://www.itying.com/images/flutter/5.png',
},
{
"url": 'https://www.itying.com/images/flutter/6.png',
},
];
class MyGridView extends StatelessWidget {
const MyGridView({super.key});
@override
Widget build(BuildContext context) {
return GridView(
// 次轴固定数量的 GridDelegate
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 数量设置3
mainAxisSpacing: 6, // 主轴间距
crossAxisSpacing: 12, // 次轴间距
childAspectRatio: 4 / 3, // 子元素宽高比率
),
// 构建子项
children: List.generate(100, (index) {
return getItem(index);
}),
);
}
// 获取子项目
Widget getItem(int index) {
var item = listData[index % 6];
return Image.network(
item["url"],
fit: BoxFit.cover,
);
}
}
效果图如下所示:
3.2 GridView.builder 构建
GridView.builder
是一种按需构建的GridView
,它只会构建当前可见区域的项,并且会在滚动过程中重复使用已构建的项,这种方式适用于大型网格布局。
class MyGridView extends StatelessWidget {
const MyGridView({super.key});
@override
Widget build(BuildContext context) {
return GridView.builder(
// 最大次轴「范围」的 GridDelegate
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 140, // 最大次轴宽度(或高度)
mainAxisSpacing: 6, // 主轴间距
crossAxisSpacing: 12, // 次轴间距
childAspectRatio: 16 / 9, // 子项宽高比率
),
itemBuilder: (context, index) {
return getItem(index);
},
itemCount: 100, // 子项数量
);
}
// 获取子项目
Widget getItem(int index) {
var item = listData[index % 6];
return Image.network(
item["url"],
fit: BoxFit.cover,
);
}
}
效果图如下所示:
3.3 GridView.count 构建
GridView.count
则是一种固定数量的构建方式,你可以指定每行(列)显示的项的数量,然后GridView
会根据这个数量自动调整布局。
class MyGridView extends StatelessWidget {
const MyGridView({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 6, // 数量设置3
mainAxisSpacing: 6, // 主轴间距
crossAxisSpacing: 12, // 次轴间距
childAspectRatio: 4 / 3, // 子项宽高比率
children: List.generate(100, (index) {
return getItem(index);
}),
);
}
// 获取子项目
Widget getItem(int index) {
var item = listData[index % 6];
return Image.network(
item["url"],
fit: BoxFit.cover,
);
}
}
效果图如下所示:
3.4 GridView.extent 构建
GridView.extent
构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent
,我们通过它可以快速的创建横轴子元素为固定最大长度的的GridView
。
class MyGridView extends StatelessWidget {
const MyGridView({super.key});
@override
Widget build(BuildContext context) {
return GridView.extent(
maxCrossAxisExtent: 140, // 最大次轴范围
mainAxisSpacing: 6, // 主轴间距
crossAxisSpacing: 12, // 次轴间距
childAspectRatio: 16 / 9, // 子项宽高比率
children: List.generate(100, (index) {
return getItem(index);
}),
);
}
// 获取子项目
Widget getItem(int index) {
var item = listData[index % 6];
return Image.network(
item["url"],
fit: BoxFit.cover,
);
}
}
效果图如下所示:
3.5 GridView.custom 构建
GridView.custom
构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent
,我们通过它可以快速的创建横轴子元素为固定最大长度的的GridView
。
class MyGridView extends StatelessWidget {
const MyGridView({super.key});
@override
Widget build(BuildContext context) {
return GridView.custom(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 140, // 最大次轴范围
mainAxisSpacing: 6, // 主轴间距
crossAxisSpacing: 12, // 次轴间距
childAspectRatio: 16 / 9, // 子项宽高比率
),
childrenDelegate: SliverChildListDelegate(
List.generate(100, (index) {
return getItem(index);
}),
),
);
}
// 获取子项目
Widget getItem(int index) {
var item = listData[index % 6];
return Image.network(
item["url"],
fit: BoxFit.cover,
);
}
}
效果图如下所示:
四、性能优化
当使用 GridView 显示大量数据时,你可以考虑以下性能优化方式:
- 使用
GridView.builder
或GridView.count
来按需构建网格项,避免一次性构建所有的项。 - 如果网格项有固定尺寸,请使用
childAspectRatio
属性来指定网格项的宽高比例,以避免动态计算尺寸带来的性能开销。 - 使用
ScrollController
来控制滚动,并使用addPostFrameCallback
在构建完成后延迟加载数据。
五、常见问题和解决方法
在使用 GridView 时,你可能会遇到一些常见的问题。以下是其中一些问题和相应的解决方法:
- GridView 无法滚动:与 ListView 类似,确保包含 GridView 的容器具有足够的空间来进行滚动,或者尝试将 GridView 的父级包装在一个可滚动的容器中。
- GridView 不显示或空白:检查是否正确设置了
itemCount
属性,并且itemBuilder
方法返回了有效的项。 - GridView 滚动不流畅:可能是由于网格项过多或构建过程中的性能问题导致的。尝试使用按需构建的
GridView.builder
或GridView.count
,并使用性能优化技术来提高滚动性能。
参考:
Flutter 中的 GridView 小部件:全面指南_flutter gridview-CSDN博客
Flutter速来系列23-1、ListView和GridView,躲不掉避不开的列表一、引言 在Flutter中,Li - 掘金
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库