Flutter CustomScrollView 自定义滚动视图 - 完全指南
Flutter CustomScrollView 是一个强大的滚动组件,可将多种滚动视图(列表、网格、固定头部等)组合成统一滚动的界面。核心特点包括支持组合滚动、吸顶效果、折叠头部和性能优化。它通过 Sliver 组件(如 SliverAppBar、SliverList、SliverGrid)实现复杂布局,典型应用包括电商首页(组合横幅、分类网格和商品列表)、个人资料页(可折叠头部)等场景。示例展
📖 什么是 CustomScrollView?
CustomScrollView 是 Flutter 中最强大的滚动组件,它可以将多种不同类型的滚动组件(列表、网格、固定头部等)组合在一起,实现复杂的滚动效果。
核心特点
- ✅ 组合多种滚动组件
- ✅ 实现吸顶效果(Sticky Header)
- ✅ 支持折叠头部(AppBar)
- ✅ 统一管理滚动行为
- ✅ 性能优秀(懒加载)
简单理解
想象一个购物 App 首页:顶部有大图横幅、下面是分类网格、再下面是商品列表。CustomScrollView 就像一个"超级容器",把这些不同的内容无缝组合在一起滚动。
🎯 基本概念
Sliver 是什么?
Sliver 是"薄片"的意思,在 Flutter 中指可以滚动的组件片段。
常用的 Sliver 组件:
SliverAppBar- 可折叠的头部SliverList- 列表SliverGrid- 网格SliverToBoxAdapter- 普通组件包装器SliverPadding- 带内边距的容器SliverFillRemaining- 填充剩余空间
📋 基本用法
最简单的例子
CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.blue,
child: Center(child: Text('顶部区域')),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('项目 $index')),
childCount: 20,
),
),
],
)
💡 10个实用案例
案例1:基础组合(固定内容 + 列表)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
// 固定的头部
SliverToBoxAdapter(
child: Container(
height: 200,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
),
),
child: Center(
child: Text(
'欢迎使用',
style: TextStyle(color: Colors.white, fontSize: 32),
),
),
),
),
// 列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
margin: EdgeInsets.all(10),
child: ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('列表项 ${index + 1}'),
subtitle: Text('这是第 ${index + 1} 个项目'),
),
);
},
childCount: 20,
),
),
],
),
),
);
}
}
应用场景:个人主页、设置页面
案例2:可折叠的 AppBar
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
// 可折叠的 AppBar
SliverAppBar(
expandedHeight: 200,
pinned: true, // 固定在顶部
flexibleSpace: FlexibleSpaceBar(
title: Text('可折叠标题'),
background: Image.network(
'https://picsum.photos/400/200',
fit: BoxFit.cover,
),
),
),
// 内容列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
height: 80,
margin: EdgeInsets.all(10),
color: Colors.primaries[index % Colors.primaries.length],
child: Center(
child: Text(
'项目 ${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
},
childCount: 30,
),
),
],
),
),
);
}
}
应用场景:文章详情、商品详情、个人资料页
案例3:列表 + 网格组合
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
// AppBar
SliverAppBar(
title: Text('购物中心'),
floating: true,
),
// 分类标题
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(15),
child: Text(
'热门分类',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
),
// 网格
SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length],
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(
'分类${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
);
},
childCount: 6,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.0,
),
),
// 商品标题
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(15),
child: Text(
'推荐商品',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
),
// 商品列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: ListTile(
leading: Container(
width: 60,
height: 60,
color: Colors.grey[300],
child: Icon(Icons.shopping_bag),
),
title: Text('商品 ${index + 1}'),
subtitle: Text('¥${(index + 1) * 99}'),
trailing: Icon(Icons.arrow_forward_ios, size: 16),
),
);
},
childCount: 20,
),
),
],
),
),
);
}
}
应用场景:电商首页、分类页面
案例4:吸顶效果(Sticky Header)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('吸顶效果'),
pinned: true,
),
// 第一组
_buildStickyHeader('分组 A'),
_buildList(10, Colors.red),
// 第二组
_buildStickyHeader('分组 B'),
_buildList(10, Colors.blue),
// 第三组
_buildStickyHeader('分组 C'),
_buildList(10, Colors.green),
],
),
),
);
}
Widget _buildStickyHeader(String title) {
return SliverPersistentHeader(
pinned: true,
delegate: _StickyHeaderDelegate(
child: Container(
color: Colors.grey[300],
padding: EdgeInsets.all(15),
child: Text(
title,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
),
);
}
Widget _buildList(int count, Color color) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
height: 60,
margin: EdgeInsets.all(5),
color: color.withOpacity(0.3),
child: Center(child: Text('项目 ${index + 1}')),
);
},
childCount: count,
),
);
}
}
class _StickyHeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
_StickyHeaderDelegate({required this.child});
Widget build(context, double shrinkOffset, bool overlapsContent) {
return child;
}
double get maxExtent => 50;
double get minExtent => 50;
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}
应用场景:通讯录、分组列表
案例5:下拉刷新
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<String> items = List.generate(20, (i) => '项目 ${i + 1}');
Future<void> _refresh() async {
await Future.delayed(Duration(seconds: 2));
setState(() {
items = List.generate(20, (i) => '刷新后 ${i + 1}');
});
}
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: RefreshIndicator(
onRefresh: _refresh,
child: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('下拉刷新'),
floating: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
margin: EdgeInsets.all(10),
child: ListTile(
title: Text(items[index]),
leading: Icon(Icons.star),
),
);
},
childCount: items.length,
),
),
],
),
),
),
);
}
}
应用场景:新闻列表、社交动态
案例6:填充剩余空间
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('填充剩余空间'),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('项目 ${index + 1}')),
childCount: 5,
),
),
// 填充剩余空间
SliverFillRemaining(
child: Container(
color: Colors.blue[100],
child: Center(
child: Text(
'这个区域会填充剩余空间',
style: TextStyle(fontSize: 18),
),
),
),
),
],
),
),
);
}
}
应用场景:空状态页面、底部固定内容
案例7:带内边距的滚动
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('带内边距'),
),
SliverPadding(
padding: EdgeInsets.all(20),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
height: 80,
margin: EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(
'项目 ${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
},
childCount: 20,
),
),
),
],
),
),
);
}
}
应用场景:需要整体内边距的列表
案例8:多个网格
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('多个网格'),
floating: true,
),
// 第一个网格 - 3列
SliverPadding(
padding: EdgeInsets.all(10),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.red[300],
borderRadius: BorderRadius.circular(10),
),
child: Center(child: Text('A${index + 1}')),
);
},
childCount: 6,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
),
),
// 分隔标题
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.all(15),
child: Text('第二组', style: TextStyle(fontSize: 20)),
),
),
// 第二个网格 - 2列
SliverPadding(
padding: EdgeInsets.all(10),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.blue[300],
borderRadius: BorderRadius.circular(10),
),
child: Center(child: Text('B${index + 1}')),
);
},
childCount: 4,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.5,
),
),
),
],
),
),
);
}
}
应用场景:相册、商品展示
案例9:复杂的首页布局
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: [
// 1. 可折叠的头部
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('首页'),
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.blue],
),
),
),
),
),
// 2. 轮播图区域
SliverToBoxAdapter(
child: Container(
height: 150,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.orange[200],
borderRadius: BorderRadius.circular(10),
),
child: Center(child: Text('轮播图', style: TextStyle(fontSize: 24))),
),
),
// 3. 分类标题
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(15),
child: Text('热门分类', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
),
),
// 4. 分类网格
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 10),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) {
final icons = [Icons.phone_android, Icons.laptop, Icons.watch, Icons.headphones];
final labels = ['手机', '电脑', '手表', '耳机'];
return Container(
decoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icons[index], size: 40),
SizedBox(height: 5),
Text(labels[index]),
],
),
);
},
childCount: 4,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
),
),
// 5. 推荐标题
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(15),
child: Text('为你推荐', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
),
),
// 6. 推荐列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 5),
child: ListTile(
leading: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length],
borderRadius: BorderRadius.circular(8),
),
),
title: Text('商品 ${index + 1}'),
subtitle: Text('这是一个很棒的商品'),
trailing: Text('¥${(index + 1) * 99}', style: TextStyle(color: Colors.red, fontSize: 18)),
),
);
},
childCount: 20,
),
),
],
),
),
);
}
}
应用场景:电商首页、新闻首页
案例10:聊天界面(反向滚动)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('聊天')),
body: Column(
children: [
Expanded(
child: CustomScrollView(
reverse: true, // 反向滚动
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final isMe = index % 2 == 0;
return Align(
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: isMe ? Colors.blue[100] : Colors.grey[300],
borderRadius: BorderRadius.circular(15),
),
child: Text('消息 ${index + 1}'),
),
);
},
childCount: 20,
),
),
],
),
),
// 输入框
Container(
padding: EdgeInsets.all(10),
color: Colors.white,
child: Row(
children: [
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: '输入消息...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
),
),
),
),
SizedBox(width: 10),
CircleAvatar(
child: IconButton(
icon: Icon(Icons.send, color: Colors.white),
onPressed: () {},
),
),
],
),
),
],
),
),
);
}
}
应用场景:即时通讯、评论区
❓ 常见问题 FAQ
1. CustomScrollView 和 ListView 有什么区别?
| 特性 | CustomScrollView | ListView |
|---|---|---|
| 灵活性 | 非常灵活,可组合多种组件 | 只能显示列表 |
| 使用难度 | 较复杂 | 简单 |
| 性能 | 优秀 | 优秀 |
| 适用场景 | 复杂布局 | 简单列表 |
2. 什么时候用 CustomScrollView?
使用场景:
- ✅ 需要组合多种滚动组件(列表+网格)
- ✅ 需要可折叠的 AppBar
- ✅ 需要吸顶效果
- ✅ 需要复杂的首页布局
不需要时:
- ❌ 只是简单的列表 → 用 ListView
- ❌ 只是简单的网格 → 用 GridView
- ❌ 固定内容 → 用 SingleChildScrollView
3. SliverToBoxAdapter 是什么?
它是一个"适配器",可以把普通组件(Container、Text 等)包装成 Sliver。
// ❌ 错误:不能直接放普通组件
CustomScrollView(
slivers: [
Container(height: 100), // 错误!
],
)
// ✅ 正确:用 SliverToBoxAdapter 包装
CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(height: 100), // 正确!
),
],
)
4. 如何实现吸顶效果?
使用 SliverPersistentHeader 配合 pinned: true:
SliverPersistentHeader(
pinned: true, // 吸顶
delegate: YourDelegate(),
)
5. SliverAppBar 的三种模式是什么?
// 1. pinned: true - 固定在顶部
SliverAppBar(pinned: true)
// 2. floating: true - 滚动时立即出现
SliverAppBar(floating: true)
// 3. snap: true - 快速展开/收起(需要配合 floating)
SliverAppBar(floating: true, snap: true)
6. 如何让 CustomScrollView 反向滚动?
CustomScrollView(
reverse: true, // 从底部开始
slivers: [...],
)
7. SliverList 和 ListView 的区别?
// ListView - 独立使用
ListView.builder(
itemBuilder: (context, index) => Widget(),
)
// SliverList - 在 CustomScrollView 中使用
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Widget(),
),
)
8. 如何在 CustomScrollView 中添加下拉刷新?
用 RefreshIndicator 包裹:
RefreshIndicator(
onRefresh: () async {
// 刷新逻辑
},
child: CustomScrollView(
slivers: [...],
),
)
📊 常用 Sliver 组件对比
| Sliver 组件 | 用途 | 示例 |
|---|---|---|
| SliverAppBar | 可折叠头部 | 商品详情页 |
| SliverList | 列表 | 新闻列表 |
| SliverGrid | 网格 | 相册 |
| SliverToBoxAdapter | 普通组件包装 | 横幅、标题 |
| SliverPadding | 带内边距 | 整体留白 |
| SliverFillRemaining | 填充剩余空间 | 空状态 |
| SliverPersistentHeader | 吸顶头部 | 分组标题 |
🎓 总结
核心要点
- CustomScrollView 用于组合多种滚动组件
- 所有子组件必须是 Sliver 类型
- 普通组件需要用 SliverToBoxAdapter 包装
- SliverAppBar 可以实现折叠效果
- 性能优秀,支持懒加载
最佳实践
- ✅ 复杂首页布局使用 CustomScrollView
- ✅ 需要吸顶效果时使用 SliverPersistentHeader
- ✅ 组合列表和网格时使用 CustomScrollView
- ✅ 使用 SliverPadding 统一管理内边距
- ❌ 简单列表不要过度使用
- ❌ 不要嵌套多个 CustomScrollView
适用场景
- 🛒 电商首页(轮播+分类+商品)
- 📱 社交媒体(头部+动态+推荐)
- 📰 新闻应用(头图+分类+列表)
- 👤 个人主页(头像+信息+动态)
- 📧 邮件列表(分组+吸顶)
- 💬 聊天界面(反向滚动)
🔧 实用代码片段
快速创建 SliverList
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => YourWidget(),
childCount: 20,
),
)
快速创建 SliverGrid
SliverGrid(
delegate: SliverChildBuilderDelegate(
(context, index) => YourWidget(),
childCount: 20,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
)
快速创建吸顶标题
SliverPersistentHeader(
pinned: true,
delegate: _HeaderDelegate(
child: Container(
color: Colors.grey[300],
child: Text('标题'),
),
),
)
提示:CustomScrollView 是 Flutter 中最强大的滚动组件,掌握它能让你实现各种复杂的滚动效果!建议从简单案例开始,逐步掌握各种 Sliver 组件的用法。
更多推荐

所有评论(0)