对于滚动的视图,我们经常需要监听它的一些滚动事件,在监听到的时候去做对应的一些事情。
比如视图滚动到底部时,我们可能希望做上拉加载更多;
比如滚动到一定位置时显示一个回到顶部的按钮,点击回到顶部的按钮,回到顶部;
比如监听滚动什么时候开始,什么时候结束;
在Flutter中监听滚动相关的内容由两部分组成:ScrollController和ScrollNotification。
1. ScrollController
在Flutter中,Widget并不是最终渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常这种监听事件以及相关的信息并不能直接从Widget中获取,而是必须通过对应的Widget的Controller来实现。
ListView、GridView的组件控制器是ScrollController,我们可以通过它来获取视图的滚动信息,并且可以调用里面的方法来更新视图的滚动位置。
另外,通常情况下,我们会根据滚动的位置来改变一些Widget的状态信息,所以ScrollController通常会和StatefulWidget一起来使用,并且会在其中控制它的初始化、监听、销毁等事件。
我们来做一个案例,当滚动到1000位置的时候,显示一个回到顶部的按钮:
- jumpTo(double offset)、animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
- ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件。
class MyHomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController _controller;
bool _isShowTop = false;
@override
void initState() {
//初始化ScrollController
_controller = ScrollController();
//监听滚动
_controller.addListener((){
var tempShowTop = _controller.offset >= 1000;
if(tempShowTop != _isShowTop){
setState(() {
_isShowTop = tempShowTop;
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ListView展示"),
),
body: ListView.builder(
itemCount: 100,
itemExtent: 60,
controller: _controller,
itemBuilder: (BuildContext context,int index){
return ListTile(
title: Text("item $index"),
);
},
),
floatingActionButton: !_isShowTop?null:FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: (){
_controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
},
),
);
}
}
2. NotificationListener
如果我们希望监听什么时候开始滚动,什么时候结束滚动,这个时候我们可以通过NotificationListener。
- NotificationListener是一个Widget,模板参数T是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。
- NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑。
- 该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为true时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false 时,则冒泡继续。
案例: 列表滚动, 并且在中间显示滚动进度
class MyHomeNotificationDemo extends StatefulWidget {
@override
_MyHomeNotificationDemoState createState() => _MyHomeNotificationDemoState();
}
class _MyHomeNotificationDemoState extends State<MyHomeNotificationDemo> {
int _progress = 0;
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notification){
//1.监听事件的类型
if(notification is ScrollStartNotification){
print("开始滚动...");
}else if(notification is ScrollUpdateNotification){
//当前滚动的位置和总长度
final currentPixel = notification.metrics.pixels;
final totalPixel = notification.metrics.maxScrollExtent;
double progress = currentPixel/totalPixel;
setState(() {
_progress = (progress*100).toInt();
});
print("正在滚动:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
}else if(notification is ScrollEndNotification){
print("滚动结束....");
}
return false;
},
child: Stack(
alignment: Alignment(.9,.9),
children: <Widget>[
ListView.builder(
itemCount: 100,
itemExtent: 80,
itemBuilder: (BuildContext context,int index){
return ListTile(title: Text("item $index"),);
},
),
CircleAvatar(
radius: 30,
child: Text("$_progress%"),
backgroundColor: Colors.black54,
),
],
),
);
}
}
学习内容来自Flutter从入门到实战