ListView
ListView 是最常用的滚动列表组件,用于显示可滚动的线性列表。
构造方式速查
| 构造方式 | 适用场景 | 性能 |
|---|---|---|
ListView() | 少量已知子组件 | 一般 |
ListView.builder() | 大量数据,按需构建 | 好 |
ListView.separated() | 需要分隔符的列表 | 好 |
ListView.custom() | 自定义子组件管理 | 最灵活 |
构造函数
ListView — 基本构造
dart
ListView({
Key? key, // 组件标识
Axis scrollDirection = Axis.vertical, // 滚动方向:vertical=垂直,horizontal=水平
bool reverse = false, // 是否反向排列
ScrollController? controller, // 滚动控制器
bool? primary, // 是否使用默认控制器(当没有 controller 时)
ScrollPhysics? physics, // 滚动物理效果
bool shrinkWrap = false, // 是否根据内容确定尺寸(而非撑满父组件)
EdgeInsetsGeometry? padding, // 内边距
double? itemExtent, // 固定每项高度(性能优化,避免动态计算)
Widget? prototypeItem, // 原型项(用于确定 itemExtent)
bool addAutomaticKeepAlives = true, // 是否自动保持子组件存活
bool addRepaintBoundaries = true, // 是否添加重绘边界
bool addSemanticIndexes = true, // 是否添加语义索引
double? cacheExtent, // 预渲染区域大小
Iterable<Widget> children = const [], // 子组件列表
int? semanticChildCount, // 语义子组件数量
DragStartBehavior dragStartBehavior = DragStartBehavior.start, // 拖拽开始行为
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, // 键盘关闭行为
String? restorationId, // 状态恢复 ID
Clip clipBehavior = Clip.hardEdge, // 裁剪行为
})ListView.builder — 按需构建(推荐大数据量)
dart
ListView.builder({
Key? key, // 组件标识
Axis scrollDirection = Axis.vertical, // 滚动方向
bool reverse = false, // 是否反向排列
ScrollController? controller, // 滚动控制器
bool? primary, // 是否使用默认控制器
ScrollPhysics? physics, // 滚动物理效果
bool shrinkWrap = false, // 是否根据内容确定尺寸
EdgeInsetsGeometry? padding, // 内边距
double? itemExtent, // 固定每项高度(性能优化)
required IndexedWidgetBuilder itemBuilder, // 列表项构建器(必填)
int? itemCount, // 列表项数量(null=无限)
bool addAutomaticKeepAlives = true, // 是否自动保持子组件存活
bool addRepaintBoundaries = true, // 是否添加重绘边界
bool addSemanticIndexes = true, // 是否添加语义索引
double? cacheExtent, // 预渲染区域大小
int? semanticChildCount, // 语义子组件数量
DragStartBehavior dragStartBehavior = DragStartBehavior.start, // 拖拽开始行为
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, // 键盘关闭行为
String? restorationId, // 状态恢复 ID
Clip clipBehavior = Clip.hardEdge, // 裁剪行为
})ListView.separated — 带分隔符
dart
ListView.separated({
Key? key, // 组件标识
Axis scrollDirection = Axis.vertical, // 滚动方向
bool reverse = false, // 是否反向排列
ScrollController? controller, // 滚动控制器
bool? primary, // 是否使用默认控制器
ScrollPhysics? physics, // 滚动物理效果
bool shrinkWrap = false, // 是否根据内容确定尺寸
EdgeInsetsGeometry? padding, // 内边距
required IndexedWidgetBuilder itemBuilder, // 列表项构建器(必填)
required IndexedWidgetBuilder separatorBuilder, // 分隔符构建器(必填)
int? itemCount, // 列表项数量(必填)
bool addAutomaticKeepAlives = true, // 是否自动保持子组件存活
bool addRepaintBoundaries = true, // 是否添加重绘边界
bool addSemanticIndexes = true, // 是否添加语义索引
double? cacheExtent, // 预渲染区域大小
int? semanticChildCount, // 语义子组件数量
DragStartBehavior dragStartBehavior = DragStartBehavior.start, // 拖拽开始行为
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, // 键盘关闭行为
String? restorationId, // 状态恢复 ID
Clip clipBehavior = Clip.hardEdge, // 裁剪行为
})常用属性速查
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scrollDirection | Axis | vertical | 滚动方向 |
reverse | bool | false | 是否反向排列 |
controller | ScrollController? | null | 滚动控制器 |
primary | bool? | null | 是否使用默认控制器 |
physics | ScrollPhysics? | null | 滚动物理效果 |
shrinkWrap | bool | false | 是否根据内容确定尺寸 |
padding | EdgeInsetsGeometry? | null | 内边距 |
itemExtent | double? | null | 固定每项高度(性能优化) |
itemBuilder | IndexedWidgetBuilder | 必填 | 列表项构建器(builder/separated) |
separatorBuilder | IndexedWidgetBuilder | 必填 | 分隔符构建器(separated 专属) |
itemCount | int? | null | 列表项数量 |
cacheExtent | double? | null | 预渲染区域大小 |
clipBehavior | Clip | hardEdge | 裁剪行为 |
physics — 滚动物理效果
| 值 | 效果 |
|---|---|
BouncingScrollPhysics() | iOS 弹性效果 |
ClampingScrollPhysics() | Android 夹紧效果(默认) |
NeverScrollableScrollPhysics() | 禁止滚动 |
AlwaysScrollableScrollPhysics() | 始终可滚动 |
快速示例
简单列表
dart
ListView(
children: const [
ListTile(title: Text('第一项')),
ListTile(title: Text('第二项')),
ListTile(title: Text('第三项')),
],
)按需构建(大数据量)
dart
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)带分隔符
dart
ListView.separated(
itemCount: items.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)水平滚动
dart
ListView(
scrollDirection: Axis.horizontal,
children: [
Container(width: 150, color: Colors.red),
Container(width: 150, color: Colors.blue),
Container(width: 150, color: Colors.green),
],
)常用场景
shrinkWrap — 在 Column 中嵌入 ListView
dart
Column(
children: [
const Text('标题'),
ListView.builder(
shrinkWrap: true, // 高度包裹内容,不撑满
physics: const NeverScrollableScrollPhysics(), // 禁止独立滚动
itemCount: 5,
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
),
],
)shrinkWrap + 大列表
shrinkWrap: true 会一次性计算所有子组件高度,数据量大时性能差。大数据量建议在外层使用 Expanded。
controller — 滚动控制
dart
final _scrollController = ScrollController();
// 滚动到底部
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
// 监听滚动
_scrollController.addListener(() {
print('滚动位置: ${_scrollController.offset}');
});
// 判断是否滚动到底部
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
print('已到底部');
}
});
// 记得在 dispose() 中释放
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}聊天列表
dart
ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: messages.length,
itemBuilder: (context, index) {
final msg = messages[index];
final isMe = msg.isMe;
return Align(
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isMe ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(12),
),
child: Text(
msg.content,
style: TextStyle(color: isMe ? Colors.white : Colors.black87),
),
),
);
},
)下拉刷新 + 上拉加载
dart
class MyListPage extends StatefulWidget {
// ...
}
class _MyListPageState extends State<MyListPage> {
List<String> _items = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
_loadMore();
}
Future<void> _loadMore() async {
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 1));
setState(() {
_items.addAll(List.generate(10, (i) => 'Item ${_items.length + i}'));
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
_items.clear();
await _loadMore();
},
child: ListView.builder(
itemCount: _items.length + (_isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _items.length) {
return const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
);
}
return ListTile(title: Text(_items[index]));
},
),
);
}
}