05_列表与滚动
Flutter 列表组件和滚动机制
ListView、GridView、下拉刷新、上拉加载更多。
ListView
基础列表(少量固定项)
ListView(
padding: const EdgeInsets.all(8),
children: [
ListTile(
leading: const Icon(Icons.inbox),
title: const Text('收件箱'),
subtitle: const Text('3 条未读'),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
const Divider(), // 分隔线
ListTile(
leading: const CircleAvatar(child: Text('A')),
title: const Text('Alice'),
subtitle: const Text('最近消息内容'),
trailing: const Text('12:00', style: TextStyle(color: Colors.grey)),
),
],
)
ListView.builder(虚拟列表,推荐)
按需渲染,只构建可见区域的 Item,适合长列表(类似 vue-virtual-scroller):
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile(
title: Text(item.title),
subtitle: Text(item.subtitle),
onTap: () {},
),
);
},
)
ListView.separated(带分隔符)
ListView.separated(
itemCount: items.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)
GridView
// 固定列数
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 列数
crossAxisSpacing: 8, // 列间距
mainAxisSpacing: 8, // 行间距
childAspectRatio: 0.75, // 宽高比
),
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(product: products[index]),
)
// 自适应列数(类似 CSS grid auto-fill)
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200, // 每格最大宽度,自动计算列数
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 20,
itemBuilder: (context, index) => Card(
child: Center(child: Text('$index')),
),
)
下拉刷新
RefreshIndicator(
onRefresh: () async {
await loadData(); // 刷新数据
},
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
),
)
上拉加载更多
class InfiniteList extends StatefulWidget {
const InfiniteList({super.key});
@override
State<InfiniteList> createState() => _InfiniteListState();
}
class _InfiniteListState extends State<InfiniteList> {
final ScrollController _scrollController = ScrollController();
final List<String> _items = List.generate(20, (i) => '第 $i 条');
bool _isLoading = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
// 距底部 200px 时触发加载
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadMore() async {
if (_isLoading) return;
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 1)); // 模拟请求
if (mounted) {
setState(() {
_items.addAll(List.generate(10, (i) => '更多第 ${_items.length + i} 条'));
_isLoading = false;
});
}
}
@override
void dispose() {
_scrollController.dispose(); // 必须释放!
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _scrollController,
itemCount: _items.length + (_isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _items.length) {
// 底部 loading 指示器
return const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
);
}
return ListTile(title: Text(_items[index]));
},
);
}
}
CustomScrollView + Sliver(高级滚动)
当需要在同一个可滚动区域混合多种布局时使用:
CustomScrollView(
slivers: [
// 可折叠顶部图片 + 标题
SliverAppBar(
expandedHeight: 250,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('商品列表'),
background: Image.network('https://example.com/banner.jpg', fit: BoxFit.cover),
),
),
// 横向分类标签(固定高度)
SliverToBoxAdapter(
child: SizedBox(
height: 60,
child: ListView(
scrollDirection: Axis.horizontal,
children: ['全部', '电子', '服装', '食品']
.map((tag) => Padding(
padding: const EdgeInsets.all(8),
child: Chip(label: Text(tag)),
))
.toList(),
),
),
),
// 商品网格
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(context, index) => ProductCard(product: products[index]),
childCount: products.length,
),
),
],
)