1419 字
7 分钟
Flutter Day04: GridView 与 CustomScrollView
Flutter Day04:GridView 与 CustomScrollView 知识点
1. GridView 组件概述
GridView 用于展示二维网格列表,适合图片墙、商品列表等网格布局页面。
核心特点:
- 支持滚动。
- 支持网格布局(按列数或按最大宽度)。
- 支持按需渲染(懒加载),只构建当前可见区域的 item,离开可视区域的 item 会被回收,性能更好。
2. GridView.count 模式(按列数布局)
常用属性
- crossAxisCount:每行(垂直滚动时)的列数。
- mainAxisSpacing:主轴方向 item 间距。
- crossAxisSpacing:交叉轴方向 item 间距。
- childAspectRatio:item 宽高比(宽 / 高)。
- padding:GridView 内边距。
- scrollDirection:滚动方向,默认 Axis.vertical。
- children:直接传入子组件列表。
示例代码
class MyGridView1 extends StatefulWidget { const MyGridView1({super.key});
@override State<MyGridView1> createState() => _MyGridViewState1();}
class _MyGridViewState1 extends State<MyGridView1> { final ScrollController _scrollController = ScrollController();
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("GridView.count")), body: GridView.count( controller: _scrollController, scrollDirection: Axis.vertical, crossAxisCount: 3, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1, padding: const EdgeInsets.all(20), children: List.generate(100, (index) { return Container( color: Colors.blue, alignment: Alignment.center, child: Text( "Item $index", style: const TextStyle(color: Colors.white, fontSize: 20), ), ); }), ), ), ); }}3. GridView.extent 模式(按最大宽度布局)
GridView.extent 通过 maxCrossAxisExtent 控制 item 最大宽度,列数会根据屏幕宽度自动变化。
例如:
- item 最大宽度为 100。
- 屏幕足够宽时每行可放 3 个。
- 屏幕变窄后会自动降为 2 个或 1 个。
常用属性
- maxCrossAxisExtent:每个 item 在交叉轴方向的最大尺寸。
- mainAxisSpacing:主轴方向 item 间距。
- crossAxisSpacing:交叉轴方向 item 间距。
- childAspectRatio:item 宽高比。
- padding:内边距。
示例代码
class MyGridView2 extends StatefulWidget { const MyGridView2({super.key});
@override State<MyGridView2> createState() => _MyGridViewState2();}
class _MyGridViewState2 extends State<MyGridView2> { final ScrollController _scrollController = ScrollController();
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("GridView.extent")), body: GridView.extent( controller: _scrollController, scrollDirection: Axis.vertical, maxCrossAxisExtent: 100, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1, padding: const EdgeInsets.all(20), children: List.generate(100, (index) { return Container( color: Colors.blue, alignment: Alignment.center, child: Text( "Item $index", style: const TextStyle(color: Colors.white, fontSize: 20), ), ); }), ), ), ); }}4. GridView.builder 模式(推荐大数据量)
GridView.builder 适合大量数据场景,按需构建 item。
常用属性
- itemBuilder:构建每个 item 的回调,参数为 context 和 index。
- itemCount:item 总数量。
- gridDelegate:网格布局规则。
- padding:内边距。
- scrollDirection:滚动方向。
gridDelegate 常见类型
- SliverGridDelegateWithFixedCrossAxisCount:按固定列数布局。
- SliverGridDelegateWithMaxCrossAxisExtent:按最大宽度布局。
示例代码
class MyGridView3 extends StatefulWidget { const MyGridView3({super.key});
@override State<MyGridView3> createState() => _MyGridViewState3();}
class _MyGridViewState3 extends State<MyGridView3> { final ScrollController _scrollController = ScrollController();
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("GridView.builder")), body: GridView.builder( controller: _scrollController, scrollDirection: Axis.vertical, padding: const EdgeInsets.all(20), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1, ), itemCount: 100, itemBuilder: (BuildContext context, int index) { return Container( color: Colors.blue, alignment: Alignment.center, child: Text( "Item $index", style: const TextStyle(color: Colors.white, fontSize: 20), ), ); }, ), ), ); }}5. CustomScrollView 组件概述
CustomScrollView 用于组合多个可滚动区域,并支持更灵活的滚动效果(如轮播图、吸顶分类、混合列表等)。
关键点:
- slivers 参数中只能放 Sliver 组件。
- 不能直接放普通 Container(可用 SliverToBoxAdapter 包裹)。
- 常见 Sliver:
- SliverToBoxAdapter:包裹普通组件。
- SliverList:列表。
- SliverGrid:网格。
- SliverPersistentHeader:可配置吸顶头部。
轮播图相关状态与属性
- _currentPage:当前轮播页索引,用于更新分页小圆点颜色。
- PageController:控制 PageView 翻页。
- onPageChanged:监听手势滑动后的页码变化。
- animateToPage:点击小圆点时平滑跳转到指定页。
SliverPersistentHeader 相关属性
- pinned: true:吸顶固定。
- floating: false:不会在反向滚动时立即浮现。
- delegate:头部布局和行为委托。
示例代码
class MyCustomScrollView extends StatefulWidget { const MyCustomScrollView({super.key});
@override State<MyCustomScrollView> createState() => _MyCustomScrollViewState();}
class _MyCustomScrollViewState extends State<MyCustomScrollView> { int _currentPage = 0; final PageController _pageController = PageController();
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text("CustomScrollView")), body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Stack( children: [ Container( height: 260, color: Colors.blue, alignment: Alignment.center, child: PageView.builder( controller: _pageController, onPageChanged: (index) { setState(() { _currentPage = index; }); }, itemBuilder: (BuildContext context, int index) { return Container( color: Colors.blue, alignment: Alignment.center, child: Text( "轮播图 $index", style: const TextStyle(color: Colors.white, fontSize: 20), ), ); }, itemCount: 10, ), ), Positioned( bottom: 0, left: 0, right: 0, height: 30, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(10, (index) { return GestureDetector( onTap: () { _pageController.animateToPage( index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); setState(() { _currentPage = index; }); }, child: Container( width: 10, height: 10, margin: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: _currentPage == index ? Colors.red : Colors.grey, shape: BoxShape.circle, ), ), ); }), ), ), ], ), ), const SliverToBoxAdapter( child: SizedBox(height: 20), ), SliverPersistentHeader( delegate: _StickyCategory(), floating: false, pinned: true, ), SliverList.separated( itemCount: 100, itemBuilder: (BuildContext context, int index) { return Container( alignment: Alignment.center, width: double.infinity, height: 100, color: Colors.blue, child: Text( "Item $index", style: const TextStyle(color: Colors.white, fontSize: 20), ), ); }, separatorBuilder: (BuildContext context, int index) { return const SizedBox(height: 10); }, ), ], ), ), ); }}6. SliverPersistentHeaderDelegate 吸顶头部
通过自定义 SliverPersistentHeaderDelegate,可以实现吸顶分类栏。
关键方法与属性
- build(context, shrinkOffset, overlapsContent):构建头部 UI。
- shrinkOffset:头部收缩量。
- overlapsContent:是否与下方内容重叠。
- maxExtent:头部最大高度。
- minExtent:头部最小高度。
- shouldRebuild(oldDelegate):是否需要重建。
- 头部配置不变时返回 false,可提升性能。
示例代码
class _StickyCategory extends SliverPersistentHeaderDelegate { @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return Container( color: Colors.white, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: 10, itemBuilder: (BuildContext context, int index) { return Container( alignment: Alignment.center, width: 100, height: 60, margin: const EdgeInsets.symmetric(horizontal: 10), color: Colors.blue, child: Text( "分类 $index", style: const TextStyle(color: Colors.white, fontSize: 20), ), ); }, ), ); }
@override double get maxExtent => 80;
@override double get minExtent => 60;
@override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return false; }}7. PageView 轮播补充知识点
在你的代码里,轮播图通过 PageView.builder 放在 SliverToBoxAdapter 中,再用 Stack + Positioned 叠加分页指示器。
这种实现的优点:
- 轮播区域属于整页滚动的一部分,可与下方 Sliver 一起滚动。
- 指示器覆盖在轮播图底部,UI 更接近电商首页效果。
- 手势滑动与点击圆点都能更新 _currentPage,交互一致。
建议补充:
- 在 State 中重写 dispose,释放 _pageController,避免内存泄漏。
@overridevoid dispose() { _pageController.dispose(); super.dispose();}8. 选型建议
- 数据量小、结构固定:优先 GridView.count。
- 需要自适应列数:使用 GridView.extent。
- 数据量大或来自接口分页:使用 GridView.builder。
- 页面有复杂滚动交互(吸顶、折叠、混合布局):使用 CustomScrollView + Sliver。
Flutter Day04: GridView 与 CustomScrollView
https://zgq1008.github.io/posts/flutter/flutter_day04/ Flutter 系列导航