在之前的文章中,每个滚动组件都有一个 controller
属性,它接受一个 ScrollController
对象。
它可以用来控制滚动 Widget 的滚动位置。
构造函数
ScrollController({
double initialScrollOffset = 0.0,
this.keepScrollOffset = true,
this.debugLabel,
})
- initialScrollOffset,初始滚动位置
- keepScrollOffset,是否保存滚动位置
- debugLabel,debug 标签,用于调试
属性和方法
属性
offset,当前滚动的位置
position,一个
ScrollController
可以同时被多个Scrollable Widget
使用,ScrollController
会为每一个Scrollable Widget
创建一个ScrollPosition
对象,这些ScrollPosition
保存在ScrollController
的positions
属性中(List)。ScrollPosition
是真正保存滑动位置信息的对象,offset
只是一个便捷属性:double get offset => position.pixels;
一个 ScrollController 虽然可以对应多个Scrollable Widget,但是有一些操作,如读取滚动位置
offset
,则需要一对一,但是我们仍然可以在一对多的情况下,通过其它方法读取滚动位置,举个例子,假设一个ScrollController同时被两个Scrollable Widget使用,那么我们可以通过如下方式分别读取他们的滚动位置:... controller.positions.elementAt(0).pixels controller.positions.elementAt(1).pixels ...
我们可以通过
controller.positions.length
来确定controller
被几个 Scrollable Widget 使用。positions,存储
position
的对象
方法
示例
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text Demo',
theme: ThemeData(primarySwatch: Colors.green),
home: MyHomePage(title: 'Text Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController _controller = new ScrollController();
@override
void initState() {
// TODO: implement initState
super.initState();
_controller.addListener(() {
print("滚动位置: ${_controller.offset}"); //打印滚动位置
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(0, 2, 0, 2),
height: 50,
width: double.infinity,
child: Text(
"${index}",
),
color: Colors.lightGreen,
);
},
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.deepOrange,
child: Icon(
Icons.add,
color: Colors.white,
),
),
);
}
}
在滚动 ListView 的时候,会打印滚动的距离:
NotificationListener 和 ScrollNotification
NotificationListener
是一个 Widget,模板参数 T
是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。
构造函数
const NotificationListener({
Key key,
@required this.child,
this.onNotification,
})
child 就是我们要监听的滚动 Widget
onNotification,用于实现监听处理逻辑,该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为 true 时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为 false 时,则冒泡继续。
对于我们监听滚动 Widget 来说,ScrollNotification
就是我们需要监听的通知类型:
NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
//处理逻辑
},
child: ListView.builder(
//略
),
)
ScrollNotification
是一个抽象类:
abstract class ScrollNotification extends LayoutChangedNotification with ViewportNotificationMixin {
/// Initializes fields for subclasses.
ScrollNotification({
@required this.metrics,
@required this.context,
});
}
常用的是它的三个实现类:
具体含义从名称上就可以看出来,不赘述,
ScrollNotification
有一个 ScrollMetrics
类型的属性 metrics
,该属性包含当前 ViewPort 及滚动位置等信息:
- pixels:当前滚动位置。
- maxScrollExtent:最大可滚动长度。
- extentBefore:滑出 ViewPort 顶部的长度;此示例中相当于顶部滑出屏幕上方的列表长度。
- extentInside:ViewPort 内部长度;此示例中屏幕显示的列表部分的长度。
- extentAfter:列表中未滑入ViewPort 部分的长度;此示例中列表底部未显示到屏幕范围部分的长度。
- atEdge:是否滑到了 Scrollable Widget 的边界(此示例中相当于列表顶或底部)。
示例
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text Demo',
theme: ThemeData(primarySwatch: Colors.green),
home: MyHomePage(title: 'Text Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController _controller = new ScrollController();
_onStartScroll(ScrollMetrics metrics) {
print("Scroll Start");
scrolling = true;
setState(() {
});
}
_onEndScroll(ScrollMetrics metrics) {
print("Scroll End");
scrolling = false;
setState(() {
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollStartNotification) {
_onStartScroll(scrollNotification.metrics);
} else if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
return false;
},
child: ListView.builder(
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(0, 2, 0, 2),
height: 50,
width: double.infinity,
child: Text(
"${index}",
),
color: Colors.lightGreen,
);
},
),
),
floatingActionButton: updateFloatingActionButton(),
);
}
bool scrolling = false;
Widget updateFloatingActionButton(){
if(scrolling){
return null;
}else{
return FloatingActionButton(
backgroundColor: Colors.deepOrange,
child: Icon(Icons.add),
);
}
}
}
在上面的例子中,当 ListView 发生滚动的时候,floatingActionButton 会隐藏,当停止滚动时,会显示,效果如下: