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); }, ), ); } }