Flutter 滚动组件ListView,GridView,Sliver以及滚动监听
前言
身是菩提树 心是明镜台 时时勤拂拭 模式染尘埃
这玩意不难,就是东西多。。。
1 看一下继承关系
class GridView extends BoxScrollView
abstract class BoxScrollView extends ScrollView
abstract class ScrollView extends StatelessWidget
2 下面是scroll_view.dart 中的代码
这个注释就是很清楚了 ,子类就是要重写buildSlivers这个方法,这是个抽象的方法。
/// Build the list of widgets to place inside the viewport.////// Subclasses should override this method to build the slivers for the inside/// of the viewport.@protectedList<Widget> buildSlivers(BuildContext context);
这是BoxScrollView 中 buildSlivers 的实现方法
@overrideList<Widget> buildSlivers(BuildContext context) {Widget sliver = buildChildLayout(context);EdgeInsetsGeometry? effectivePadding = padding;if (padding == null) {final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);if (mediaQuery != null) {// Automatically pad sliver with padding from MediaQuery.final EdgeInsets mediaQueryHorizontalPadding =mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0);final EdgeInsets mediaQueryVerticalPadding =mediaQuery.padding.copyWith(left: 0.0, right: 0.0);// Consume the main axis padding with SliverPadding.effectivePadding = scrollDirection == Axis.vertical? mediaQueryVerticalPadding: mediaQueryHorizontalPadding;// Leave behind the cross axis padding.sliver = MediaQuery(data: mediaQuery.copyWith(padding: scrollDirection == Axis.vertical? mediaQueryHorizontalPadding: mediaQueryVerticalPadding,),child: sliver,);}}if (effectivePadding != null) {sliver = SliverPadding(padding: effectivePadding, sliver: sliver);}return <Widget>[sliver];}
从下面这行代码可以看出来,BoxScrollView 只有一个Sliver
Widget sliver = buildChildLayout(context);
而这个Sliver 来自调用 buildChildLayout,同时这个方法又是一个未实现的抽象方法,所以那么需要子类去实现这个方法,那么换言之1就是 Gridview 和 Listview 去实现这个方法
这个就是GridView 中实现的方法,返回的是一个SliverGrid 本质是也是一个
@overrideWidget buildChildLayout(BuildContext context) {return SliverGrid(delegate: childrenDelegate,gridDelegate: gridDelegate,);}
这是 ListView 的实现的方法
Widget buildChildLayout(BuildContext context) {if (itemExtent != null) {return SliverFixedExtentList(delegate: childrenDelegate,itemExtent: itemExtent!,);} else if (prototypeItem != null) {return SliverPrototypeExtentList(delegate: childrenDelegate,prototypeItem: prototypeItem!,);}return SliverList(delegate: childrenDelegate);}
一 ListView
1 第一种构造方法
ListView()
ListView(padding: EdgeInsets.all(10),itemExtent: 100,children: List.generate(100, (index) {return Text("data--${index}");}),);
2 第二种构造方法
ListView.builder
_demo2() {return ListView.builder(itemExtent: 100,itemBuilder: (context, index) {return Text("data");},);}
3 第三种构造方法
ListView.separated
_demo3() {return ListView.separated(itemBuilder: (context, index) {return Text("data---${index}");},separatorBuilder: (context, index) {// 返回分割的区域 thickness 为线高return Divider(color: Colors.redAccent,height: 10,thickness: 10,);},itemCount: 100);}
二 GridView
网格布局
1 第一种构造方法
demo1() {return GridView(padding: EdgeInsets.only(left: 10, right: 10),gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(// 交叉轴的间距crossAxisSpacing: 10,// 主轴的间距mainAxisSpacing: 10,// 宽高比childAspectRatio: 16 / 18,// 交叉轴的个数crossAxisCount: 3),children: List.generate(100, (index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);}));}
2 第二种构造方法
_demo2() {return GridView.builder(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,childAspectRatio: 1 / 1,mainAxisSpacing: 10,crossAxisSpacing: 8,),itemBuilder: (context, index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);},);}
3 第三种构造方法
_demo3() {return GridView.count(mainAxisSpacing: 10,crossAxisSpacing: 10,crossAxisCount: 3,children: List.generate(100, (index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);}));}
三 Slivers
这个单词翻译成 碎片,可以将一个独立的滚动视图当做一个小裂片来使用。
这个主要是实现一些复杂的布局。
-
SliverList:类似于我们之前使用过的ListView;
-
SliverFixedExtentList:类似于SliverList只是可以设置滚动的高度;
-
SliverGrid:类似于我们之前使用过的GridView;
-
SliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距;
-
SliverAppBar:添加一个AppBar,通常用来作为CustomScrollView的HeaderView;
-
SliverSafeArea:设置内容显示在安全区域(比如不让齐刘海挡住我们的内容)
示例一
放置多个滚动组件在里面。
_demo1() {return CustomScrollView(// 设置内边距slivers: [// // 第一个部分SliverPadding(padding: EdgeInsets.all(8),sliver: SliverSafeArea(// 这个安全区域可以设置上下左右bottom: false,sliver: SliverGrid(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,crossAxisSpacing: 8,mainAxisSpacing: 8,childAspectRatio: 16 / 18),delegate: SliverChildBuilderDelegate(((context, index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);}), childCount: 6))),),// 第二个部分SliverGrid(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 8,mainAxisSpacing: 8,childAspectRatio: 16 / 18),delegate: SliverChildBuilderDelegate(((context, index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);}),// 一共多少个itemchildCount: 2)),// 第三个部分SliverGrid(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4,crossAxisSpacing: 8,mainAxisSpacing: 8,childAspectRatio: 16 / 18),delegate: SliverChildBuilderDelegate(((context, index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);}),// 一共多少个itemchildCount: 40)),],);}
示例2
这里还可以设置一个SliverAppBar, 设置很多属性
_demo2() {return CustomScrollView(slivers: [// 用这个导航 上面的导航就可以删掉 这个导航可以随着内容滚动而滚动SliverAppBar(// 扩展一个高度 设置一个图片expandedHeight: 200,flexibleSpace: FlexibleSpaceBar(title: Text("Helo"),background: Image.asset("images/movie_1.png",fit: BoxFit.cover,)),// 是否随着内容滚动pinned: true,),// 第一个是SliverGridSliverPadding(padding: EdgeInsets.all(8),sliver: SliverGrid(delegate: SliverChildBuilderDelegate(((context, index) {return Container(color: Color.fromARGB(255, Random().nextInt(256),Random().nextInt(256), Random().nextInt(256)),);}), childCount: 3),gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,mainAxisSpacing: 8,crossAxisSpacing: 8,childAspectRatio: 1 / 1),),),// 第二个使用SliverListSliverList(delegate: SliverChildBuilderDelegate((context, index) {return ListTile(leading: Icon(Icons.phone),title: Text("第---${index}-----行"),);}, childCount: 100))],);}
四 滚动监听
1 使用controller 进行监听
不管我们用ListView 还是GridView 还是CustomScrollView,
都有一个controller 属性
class _SWListenDemoState extends State<SWListenDemo> {// 设置一个controllerScrollController _controller = ScrollController(initialScrollOffset: 100);@overridevoid initState() {// TODO: implement initStatesuper.initState();// 回到顶部可以 调用的方法// _controller.animateTo(0, duration: Duration(seconds: 2), curve: Curves.bounceIn);_controller.addListener(() {// 监听滚动的位置print("${_controller.offset}");});}
2 NotificationListener
如果我们希望监听什么时候开始滚动,什么时候结束滚动,这个时候我们可以通过NotificationListener
。
class NotificationListener<T extends Notification> extends ProxyWidget
-
NotificationListener是一个Widget,模板参数T是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。
-
NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑。
-
该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为
true
时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false
时,则冒泡继续。
Widget build(BuildContext context) {return NotificationListener(onNotification: (ScrollStartNotification notification) {// 当前滚动的位置 和 总长度final currentPixels = notification.metrics.pixels;final totalPixels = notification.metrics.maxScrollExtent;double progress = currentPixels / totalPixels;print("开始滚动了.....${progress} -- ${currentPixels}");return false;},child: ListView(// controller: _controller,children: List.generate(100, (index) {return ListTile(leading: Icon(Icons.time_to_leave),title: Text("---DiDi---"),);}),));}