https://docs.flutter.io/flutter/widgets/GridView-class.html
CustomScrollView是可以使用Sliver来自定义滚动模型(效果)的组件。它可以包含多种滚动模型。
使用场景:
- 有一个页面,顶部需要一个
GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体。如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的,所以这时就需要一个”胶水”,把这些彼此独立的可滚动组件”粘”起来。 - ListView和GridView相互嵌套场景,ListView嵌套GridView时,需要给GridView指定高度,但我们希望高度随内容而变化(不指定),ListView和GridView使用同一个滚动效果。
- 一个页面顶部是AppBar,然后是GridView,最后是ListView,这3个区域以整体来滚动,AppBar具有吸顶效果。
CustomScrollView的功能就相当于“胶水”,将多个组件粘合在一起,具统一的滚动效果。
可滚动组件的Sliver版
Sliver有细片、薄片之意,在Flutter中,Sliver通常指可滚动组件子元素(就像一个个薄片一样)。但是在CustomScrollView中,需要粘起来的可滚动组件就是CustomScrollView的Sliver了,如果直接将ListView、GridView作为CustomScrollView是不行的,因为它们本身是可滚动组件而并不是Sliver!因此,为了能让可滚动组件能和CustomScrollView配合使用,Flutter提供了一些可滚动组件的Sliver版,如SliverList、SliverGrid等。
实际上Sliver版的可滚动组件和非Sliver版的可滚动组件最大的区别就是前者不包含滚动模型(自身不能再滚动),而后者包含滚动模型 ,也正因如此,CustomScrollView才可以将多个Sliver”粘”在一起,这些Sliver共用CustomScrollView的Scrollable,所以最终才实现了统一的滑动效果。
Sliver系列Widget比较多,我们不会一一介绍,读者只需记住它的特点,需要时再去查看文档即可。上面之所以说“大多数”Sliver都和可滚动组件对应,是由于还有一些如SliverPadding、SliverAppBar等是和可滚动组件无关的,它们主要是为了结合CustomScrollView一起使用,这是因为CustomScrollView的子组件必须都是Sliver。
API 定义
CustomScrollView({Key key,Axis scrollDirection = Axis.vertical,bool reverse = false,ScrollController controller,bool primary,ScrollPhysics physics,bool shrinkWrap = false,Key center,double anchor = 0.0,double cacheExtent,this.slivers = const <Widget>[],int semanticChildCount,DragStartBehavior dragStartBehavior = DragStartBehavior.start,String restorationId,Clip clipBehavior = Clip.hardEdge,})
示例1:相互嵌套场景
在实际业务场景中经常见到这样的布局,顶部是网格布局(GridView),然后是列表布局(ListView),滚动的时候做为一个整体,此场景是无法使用GridView+ListView来实现的,而是需要使用CustomScrollView+SliverGrid+SliverList来实现,实现代码如下:
CustomScrollView(slivers: [SliverGrid.count(crossAxisCount: 4,childAspectRatio: 2,children: List.generate(8,(index) => Container(color: Colors.primaries[index % Colors.primaries.length],child: Text('$index', textScaleFactor: 2),),).toList(),),SliverList(delegate: SliverChildBuilderDelegate((context, index) => Container(height: 100,color: Colors.primaries[index % Colors.primaries.length],child: Text('$index', textScaleFactor: 2),),childCount: 20,),),],),
示例2:顶部AppBar吸顶示例
实际项目中页面顶部是AppBar,然后是GridView,最后是ListView,这3个区域以整体来滚动,AppBar具有吸顶效果,此效果也是我们经常遇到的,用法如下:
CustomScrollView(slivers: [SliverAppBar(pinned: true,expandedHeight: 230,flexibleSpace: FlexibleSpaceBar(title: Text('我是吸顶后的标题'),background: Image.network('https://images.h128.com/upload/202012/20/202012201302083641.jpg',fit: BoxFit.cover,),),),// 下面代码同示例1SliverGrid.count(...),SliverList(...),],),
示例3:Sliver-Sticky效果
tab1.dart
import 'package:flutter/material.dart';import 'package:app1/demos/tabs/tab1_view1.dart';import 'package:app1/demos/tabs/tab1_view2.dart';import 'package:flutter_easyrefresh/easy_refresh.dart';class Tab1 extends StatefulWidget {@override_Tab1State createState() => _Tab1State();}class _Tab1State extends State<Tab1> with SingleTickerProviderStateMixin {ScrollController _scrollViewController;int _tabIndex = 0;List<Map> _list = [{'index': 0,'title': 'tab1','key': tab1View1ChildKey,'child': Tab1View1(key: tab1View1ChildKey)},{'index': 1,'title': 'tab2','key': tab1View2ChildKey,'child': Tab1View2(key: tab1View2ChildKey)},];@overridevoid initState() {super.initState();_scrollViewController = ScrollController();// 监听滚动事件,打印滚动位置// _scrollViewController.addListener(() {// print(_scrollViewController.offset);// });}@overrideWidget build(BuildContext context) {print('_tabIndex $_tabIndex');var currentTab = _list[_tabIndex];return Scaffold(body: Container(width: double.infinity,height: double.infinity,child: EasyRefresh.custom(scrollController: _scrollViewController,firstRefresh: true, // 是否需要首次刷新firstRefreshWidget: Center(child: Text('页面加载中...')), // 首次刷新加载组件onRefresh: () async {await Future.delayed(Duration(seconds: 2), () {if (mounted) {var tabViewChild = currentTab['key'].currentState;tabViewChild.getData(isRefresh: true);}});},onLoad: () async {await Future.delayed(Duration(seconds: 2), () {if (mounted) {// var tabViewChild = currentTab['key'].currentState;// tabViewChild.getData(isRefresh: false, onSuccess);}});},slivers: [SliverAppBar(pinned: true,elevation: 0,expandedHeight: 250,backgroundColor: Colors.green,flexibleSpace: FlexibleSpaceBar(title: Text('Sliver-sticky效果'),),),SliverList(delegate: SliverChildListDelegate([Container(height: 200,color: Colors.red,child: Text('asdfs'),),]),),SliverPersistentHeader(pinned: true,delegate: _buildPersistentHeader(child: Container(color: Colors.yellow,height: 60,child: Row(children: _list.map((item) => InkWell(child: Text(item['title']),onTap: () {setState(() {_tabIndex = item['index'];_scrollViewController.jumpTo(0); //切换tab后,跳回顶部});},),).toList(),),),),),SliverToBoxAdapter(child: Container(height: 1000.0,child: currentTab['child'],),),],),),);}}class _buildPersistentHeader extends SliverPersistentHeaderDelegate {final child;_buildPersistentHeader({@required this.child});// shrinkOffset 偏移量@overrideWidget build(BuildContext context, double shrinkOffset, bool overlapsContent) {return this.child;}// 最大上滑高度@override// double get maxExtent => this.child.preferredSize.height;double get maxExtent => 60;// 最小上滑高度@override// double get minExtent => this.child.preferredSize.height;double get minExtent => 60;@overridebool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {return true;}}
tab1_view1.dart
import 'package:flutter/material.dart';GlobalKey<_Tab1View1State> tab1View1ChildKey = GlobalKey();class Tab1View1 extends StatefulWidget {Tab1View1({Key key}) : super(key: key);@override_Tab1View1State createState() => _Tab1View1State();}class _Tab1View1State extends State<Tab1View1> {@overridevoid initState() {super.initState();print('view1-initState');}@overridevoid dispose() {print('view2-dispose');super.dispose();}getData({isRefresh = false}) {print('view1-getData');}@overrideWidget build(BuildContext context) {return Container(child: Text('view1'),);}}
