Flutter 常用的滚动组件包括:
- ListView:在一个可滚动的列表中显示一系列的子控件。
- GridView:在一个网格布局中显示一系列的子控件。
- SingleChildScrollView:在一个可滚动的视图中显示单个子控件。
- CustomScrollView:自定义滚动模型的可滚动视图,可以同时包含多种滚动模型,如 ListView、GridView 和 SliverAppBar 等。
ListView
ListView 是最常用的可滚动列表组件之一。使用 ListView 可以轻松地在一个可滚动的列表中显示一系列的子控件。
ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.map),
title: Text('Map'),
),
ListTile(
leading: Icon(Icons.photo_album),
title: Text('Album'),
),
ListTile(
leading: Icon(Icons.phone),
title: Text('Phone'),
),
],
);
当需要显示大量数据时,可以使用 ListView.builder 来避免同时创建所有子控件的问题,这样只会在屏幕上显示当前可见区域内的子控件。
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item ${items[index]}'),
);
},
);
GridView
GridView 是另一种常用的可滚动列表组件,它将子控件排列成网格布局。
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
children: <Widget>[
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
Container(color: Colors.yellow),
],
);
与 ListView 一样,当需要显示大量数据时,可以使用 GridView.builder 来避免同时创建所有子控件的问题。
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
itemCount: items.length,
itemBuilder: (context, index) {
return Container(color: items[index]);
},
);
SingleChildScrollView
SingleChildScrollView 是一个可滚动的视图,它只能包含单个子控件。
SingleChildScrollView(
child: Column(
children: <Widget>[
Container(height: 100, color: Colors.red),
Container(height: 100, color: Colors.green),
Container(height: 100, color: Colors.blue),
Container(height: 100, color: Colors.yellow),
],
),
);
与 ListView 不同,SingleChildScrollView 不会自动回收不可见区域的子控件。因此,应该尽可能减少子控件的数量,并将其放到层次结构较浅的位置。
CustomScrollView
CustomScrollView 是自定义滚动模型的可滚动视图,可以同时包含多种滚动模型,如 ListView、GridView 和 SliverAppBar 等。
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Title'),
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'https://picsum.photos/200/300',
fit: BoxFit.cover,
),
),
),
SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(title: Text('Item $index'));
},
childCount: 20,
),
),
],
);
优化
使用更轻量级的滚动组件
SingleChildScrollView 比 ListView 更轻松,因为它只有一个子控件。如果列表较短,可以考虑使用 SingleChildScrollView。
使用 ListView.builder 或 GridView.builder
当需要显示大量数据时,使用 ListView.builder 或 GridView.builder 可以避免同时创建所有子控件的问题,仅在屏幕上显示当前可见区域内的子控件。
优化子控件的构建过程
对于静态的子控件,可以使用 const 构造函数创建。对于动态的子控件,可以将部分子控件放到 Stateful 组件中管理,或使用 StatefulBuilder 在需要更新的子树中包装子控件。
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return StatefulBuilder(
builder: (context, setState) {
return ListTile(
title: Text('Item ${items[index]}'),
trailing: IconButton(
icon: Icon(Icons.favorite),
color: isFavorite(index) ? Colors.red : null,
onPressed: () {
setState(() {
setFavorite(index, !isFavorite(index));
});
},
),
);
},
);
},
);
避免在滚动时频繁进行重绘
使用 AutomaticKeepAliveClientMixin 可以避免不必要的重绘,将需要保持状态的子控件放到 Stateful 组件中,并在组件中实现 wantKeepAlive 和 build 方法即可。
class MyListItem extends StatefulWidget {
final int index;
const MyListItem({Key? key, required this.index}) : super(key: key);
@override
_MyListItemState createState() => _MyListItemState();
}
class _MyListItemState extends State<MyListItem>
with AutomaticKeepAliveClientMixin {
bool _isFavorite = false;
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return ListTile(
title: Text('Item ${widget.index}'),
trailing: IconButton(
icon: Icon(Icons.favorite),
color: _isFavorite ? Colors.red : null,
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
},
),
);
}
}
合理使用 ScrollController 和 NotificationListener
使用 ScrollController 可以监听滚动事件,及时释放资源和加载数据。使用 NotificationListener 可以监听滚动事件并执行自定义操作。
class MyListView extends StatefulWidget {
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
final _controller = ScrollController();
bool get _isScrolledToBottom {
return _controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange;
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollEndNotification && _isScrolledToBottom) {
loadMoreData();
}
return false;
},
child: ListView.builder(
controller: _controller,
itemCount: items.length,
itemBuilder: (context, index) {
return MyListItem(index: index);
},
),
);
}
}