在Flutter 中 ListView 组件是Flutter中的核心组件之一,在日常开发中也是使用比较频繁的一个组件,相比Column和row,它自带滚动效果,而且还支持动态加载数据。它非常适合展示长列表数据,因为它只构建那些当前可见的项,这大大提高了性能。
一、基本概念
ListView 是 Flutter 中最常用的滚动列表组件,用于高效展示大量数据。支持垂直和水平滚动,提供多种构造方法适应不同场景。
二、核心构造方法
1. 默认构造 ListView()
直接传入子组件列表,适合少量静态数据:
ListView(
children: <Widget>[
ListTile(title: Text("Item 1")),
ListTile(title: Text("Item 2")),
// ...
],
)
2. 动态构建 ListView.builder()
通过索引动态生成子项,适合大数据集(懒加载):
ListView.builder(
itemCount: 100, // 总项数
itemBuilder: (context, index) {
return ListTile(title: Text("Item $index"));
},
)
3. 带分隔线 ListView.separated()
自动添加自定义分隔线,适合需要间隔的列表:
ListView.separated(
itemCount: 50,
separatorBuilder: (_, __) => Divider(height: 1),
itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
)
4. 自定义布局 ListView.custom()
完全控制子组件布局,需指定 childrenDelegate:
通过自己的逻辑去展示子组件,尤其是针对一些包含多种不同类型布局的列表。
ListView.custom(
childrenDelegate: SliverChildBuilderDelegate((_, index) {
if (index % 2 == 0) {
return Container(
color: Colors.green,
alignment: Alignment.centerLeft,
height: 60,
child: Text('Item$index'),
);
} else {
return ListTile(title: Text("Item $index"));
}
}, childCount: 100),
),
三、核心属性
1. 滚动方向:scrollDirection
Axis.vertical(默认垂直滚动)Axis.horizontal(水平滚动)
ListView(
scrollDirection: Axis.horizontal,
children: [Container(width: 100), Container(width: 100)],
)
2. 滚动控制:controller
通过 ScrollController 实现滚动监听或跳转:
final controller = ScrollController();
ListView(
controller: controller,
children: [...],
)
// 跳转到底部
controller.jumpTo(controller.position.maxScrollExtent);
3. 边界效果:physics
控制滚动行为:
BouncingScrollPhysics():iOS 弹性效果
ClampingScrollPhysics():Android 夹紧效果
NeverScrollableScrollPhysics():禁用滚动
4. 内边距:padding
设置列表内容的内边距:
ListView(padding: EdgeInsets.all(16))
5. 自适应尺寸:shrinkWrap
解决嵌套滚动时的尺寸冲突(默认 false):
有些场景,比如要在column里使用ListView的时候,必须把这个参数设置为true,否则会报错。
Column(
children: [
ListView(
// shrinkWrap: true, 这个注释掉就报错了
children: <Widget>[
ListTile(title: Text("Item 1")),
ListTile(title: Text("Item 2")),
// ...
],
),
],
),
报错信息
6. 预加载:cacheExtent
设置预加载区域的高度(像素):
ListView(cacheExtent: 500) // 提升滚动流畅度
四、动态数据加载
1. 配合 RefreshIndicator和ScrollController 使用实现分页加载和下拉刷新
- 分页加载
// 通过监听滚动位置判断是否要加载更多数据。
void _scrollListener() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_isLoading) {
_loadData();
}
}
- 下拉刷新
RefreshIndicator(
onRefresh: () => _loadData(isRefresh: true), //刷新数据的关键方法
child: ListView.builder(
controller: _scrollController,
itemCount: _items.length + (_isLoading ? 1 : 0), // 如果正在加载,增加一个额外的item用于显示加载指示器
itemBuilder: (context, index) {
if (index == _items.length) {
return Center(child: CircularProgressIndicator()); // 加载更多时的指示器
}
return ListTile(
title: Text(_items[index]),
);
},
),
),
- 完整实例
class _SearchPageState extends State<SearchPage> {
final ScrollController _scrollController = ScrollController();
final List<String> _items = List.generate(20, (index) => "Item ${index + 1}");
bool _isLoading = false;
@override
void initState() {
super.initState();
// 监听滚动事件以便实现上拉加载更多
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
// 模拟数据加载
Future<void> _loadData({bool isRefresh = false}) async {
if (!isRefresh) {
setState(() {
_isLoading = true;
});
}
// 模拟网络请求延迟
await Future.delayed(Duration(seconds: 2));
if (isRefresh) {
setState(() {
_items.clear();
_items.addAll(List.generate(20, (index) => "Refreshed Item ${index + 1}"));
});
} else {
setState(() {
_items.addAll(List.generate(10, (index) => "Item ${_items.length + index + 1}"));
});
}
setState(() {
_isLoading = false;
});
}
// 通过监听滚动位置判断是否要加载更多数据。
void _scrollListener() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_isLoading) {
_loadData();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('下拉刷新与上拉加载示例'),
),
body: RefreshIndicator(
onRefresh: () => _loadData(isRefresh: true),
child: ListView.builder(
controller: _scrollController,
itemCount: _items.length + (_isLoading ? 1 : 0), // 如果正在加载,增加一个额外的item用于显示加载指示器
itemBuilder: (context, index) {
if (index == _items.length) {
return Center(child: CircularProgressIndicator()); // 加载更多时的指示器
}
return ListTile(
title: Text(_items[index]),
);
},
),
),
);
}
}
五、性能优化技巧
1. 使用 const 构造函数
减少 Widget 重建开销:
ListView(
children: [
const ListTile(title: Text("Fixed Item 1")),
const ListTile(title: Text("Fixed Item 2")),
],
)
2. 懒加载图片
使用 ListView.builder + Image.network 的 loadingBuilder:
ListView.builder(
itemBuilder: (_, index) {
return Image.network(
imageUrls[index],
loadingBuilder: (_, child, progress) {
return progress == null ? child : CircularProgressIndicator();
},
);
},
)
3. 保持列表项状态
使用 AutomaticKeepAliveClientMixin:
class KeepAliveItem extends StatefulWidget {
@override
_KeepAliveItemState createState() => _KeepAliveItemState();
}
class _KeepAliveItemState extends State<KeepAliveItem>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // 保持状态
@override
Widget build(BuildContext context) {
super.build(context);
return ListTile(...);
}
}
扩展用法
聊天列表(反向滚动)
让数据从下往上展示
ListView.builder(
reverse: true, // 从底部开始
itemCount: messages.length,
itemBuilder: (_, index) => ChatBubble(message: messages[index]),
)
Widget ChatBubble({Map? message}) {
if(message?['type'] == '2') {
return Container(
width: 200,
height: 60,
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(color: Colors.green, width: 1)
),
alignment: Alignment.centerLeft,
child: Text(message?['message'], style: TextStyle(color: Colors.white),),
);
} else {
return Container(
width: 300,
height: 60,
alignment: Alignment.centerRight,
child: Text(message?['message'], textAlign: TextAlign.right,),
);
}
}
总结
ListView 是 Flutter 中处理滚动列表的核心组件,通过合理选择构造方法(builder/separated/custom)和优化手段(const/AutomaticKeepAlive),可以高效处理动态数据和大数据集。关键注意点:
- 大数据集必须使用
ListView.builder避免内存溢出 - 嵌套滚动时优先使用
CustomScrollView - 通过
cacheExtent和itemExtent提升性能 - 结合
RefreshIndicator和分页逻辑实现完整数据流