本文主要介绍了 Flutter 监听滚动控件的滚动事件和一些滚动控制器的用法,最后实现 appBar 滚动渐变,若有不当之处敬请指正。git
阅读本文大约须要 5 分钟github
在 Flutter 中滚动监听通常能够采用两种方式来实现,分别是 ScrollController
和 NotificationListener
这两种方式。数组
介绍一下ScrollController
经常使用的属性和方法:浏览器
offset
:可滚动组件当前的滚动位置。jumpTo(double offset)
跳转到指定位置,offset
为滚动偏移量。animateTo(double offset,@required Duration duration,@required Curve curve)
同 jumpTo(double offset)
同样,不一样的是 animateTo
跳转时会执行一个动画,须要传入执行动画须要的时间和动画曲线。ScrollPosition是用来保存可滚动组件的滚动位置的。一个 ScrollController 对象可能会被多个可滚动的组件使用,markdown
ScrollController 会为每个滚动组件建立一个 ScrollPosition 对象来存储位置信息。ScrollPosition 中存储的是在 ScrollController 的 positions 属性里面,他是一个 List<ScrollPosition>
数组,在 ScrollController 中真正保存位置信息的就是 ScrollPosition,而 offset 只是一个便捷使用的属性。查看源码中能够发现 offset 获取就是从 ScrollPosition 中获取的。app
/// Returns the attached [ScrollPosition], from which the actual scroll offset
/// of the [ScrollView] can be obtained.
/// Calling this is only valid when only a single position is attached.
ScrollPosition get position {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
return _positions.single;
}
/// The current scroll offset of the scrollable widget.
/// Requires the controller to be controlling exactly one scrollable widget.
double get offset => position.pixels;
复制代码
一个 ScrollController
虽然能够对应多个可滚动组件,可是读取滚动位置 offset
,则须要一对一读取。在一对多的状况下,咱们可使用其余方法来实现读取滚动位置。假设如今一个 ScrollController
对应了两个能够滚动的组件,那么能够经过 position.elementAt(index)
来获取 ScrollPosition
,从而得到 offset
:less
controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
复制代码
ScrollPosition
有两个经常使用方法:分别是 animateTo()
和 jumpTo()
,他们才是真正控制跳转到滚动位置的方法,在 ScrollController 中这两个同名方法,内部最终都会调用 ScrollPosition 这两个方法。ide
Future<void> animateTo(
double offset, {
@required Duration duration,
@required Curve curve,
}) {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
final List<Future<void>> animations = List<Future<void>>(_positions.length);
for (int i = 0; i < _positions.length; i += 1)
// 调用 ScrollPosition 中的 animateTo 方法
animations[i] = _positions[i].animateTo(offset, duration: duration, curve: curve);
return Future.wait<void>(animations).then<void>((List<void> _) => null);
}
复制代码
ScrollController
还有其余比较重要的三个方法:布局
createScrollPosition
:当 ScrollController
和可滚动组件关联时,可滚动组件首先会调 ScrollController
的 createScrollPosition
方法来建立一个ScrollPosition
来存储滚动位置信息。ScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition);
复制代码
createScrollPosition
方法以后,接着会调用 attach
方法来将建立号的 ScrollPosition
信息添加到 positions
属性中,这一步称为“注册位置”,只有注册后animateTo()
和 jumpTo()
才能够被调用。void attach(ScrollPosition position);
复制代码
detach()
方法,将其 ScrollPosition
对象从 ScrollController
的positions
属性中移除,这一步称为“注销位置”,注销后 animateTo()
和 jumpTo()
将不能再被调用。void detach(ScrollPosition position);
复制代码
Flutter Widget 树中子 Widge t能够经过发送通知(Notification)与父(包括祖先) Widget 进行通讯,父级组件能够经过 NotificationListener
组件来监听本身关注的通知,这种通讯方式相似于 Web 开发中浏览器的事件冒泡,在 Flutter 中就沿用了“冒泡”这个术语,称为通知冒泡学习
通知冒泡和用户触摸事件冒泡是类似的,但有一点不一样:通知冒泡能够停止,但用户触摸事件不行。
Flutter 中不少地方使用了通知,如可滚动组件(Scrollable Widget)滑动时就会分发滚动通知(ScrollNotification),而 Scrollbar
正是经过监听 ScrollNotification
来肯定滚动条位置的。
switch (notification.runtimeType){
case ScrollStartNotification: print("开始滚动"); break;
case ScrollUpdateNotification: print("正在滚动"); break;
case ScrollEndNotification: print("滚动中止"); break;
case OverscrollNotification: print("滚动到边界"); break;
}
复制代码
其中 ScrollStartNotification
和 ScrollUpdateNotification
等都是继承 ScrollNotification
类的,不一样类型的通知子类会包含不一样的信息,ScrollUpdateNotification
有一个 scrollDelta
属性,它记录了移动的位移。
NotificationListener
时继承 StatelessWidget
类的额,左右咱们能够直接在放置在Widget 数中,经过里面的 onNotification
能够指定一个模板参数,该模板参数类型必须是继承自Notification
,能够显式指定模板参数时,好比通知的类型为滚动结束通知:
NotificationListener<ScrollEndNotification>
复制代码
这个时候 NotificationListener
便只会接收该参数类型的通知。
onNotification
回调为通知处理回调,他的返回值时布尔类型(bool),当返回值为 true
时,阻止冒泡,其父级 Widget 将再也收不到该通知;当返回值为 false
时继续向上冒泡通知。
首先这两种方式均可以实现对滚动的监听,可是他们仍是有一些区别:
ScrollController
能够控制滚动控件的滚动,而 NotificationListener
是不能够的。NotificationListener
能够在从可滚动组件到widget树根之间任意位置都能监听,而ScrollController
只能和具体的可滚动组件关联后才能够。NotificationListener
在收到滚动事件时,通知中会携带当前滚动位置和ViewPort的一些信息,而ScrollController
只能获取当前滚动位置。建立滚动所需的界面,一个 Scaffold
组件 body
里面方式一个 Stack
的层叠小部件,里面放置一个listview
,和自定义的 appBar
;floatingActionButton
放置一个返回顶部的悬浮按钮。
Scaffold(
body: Stack(
children: <Widget>[
MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
// ScrollController 关联滚动组件
controller: _controller,
itemCount: 100,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
height: 200,
child: Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
"http://via.placeholder.com/350x150",
fit: BoxFit.fill,
);
},
itemCount: 3,
autoplay: true,
pagination: new SwiperPagination(),
),
);
}
return ListTile(
title: Text("ListTile:$index"),
);
},
),
),
Opacity(
opacity: toolbarOpacity,
child: Container(
height: 98,
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.only(top: 30.0),
child: Center(
child: Text(
"ScrollerDemo",
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
),
),
),
)
],
),
floatingActionButton: !showToTopBtn
? null
: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
_controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.ease);
},
),
)
复制代码
建立 ScrollController
对象,在初始化中添加对滚动的监听,并和 ListView
这个可滚动小部件进行关联:
ScrollController _controller = new ScrollController();
@override
void initState() {
_controller.addListener(() {
print(_controller.offset); //打印滚动位置
})
}
复制代码
double t = _controller.offset / DEFAULT_SCROLLER;
if (t < 0.0) {
t = 0.0;
} else if (t > 1.0) {
t = 1.0;
}
setState(() {
toolbarOpacity = t;
});
复制代码
更具滚动的高度和当前 floatingActionButton
的现实状态,判断 floatingActionButton
是否须要展现:
if(_controller.offset < DEFAULT_SHOW_TOP_BTN && showToTopBtn){
setState(() {
showToTopBtn = false;
});
}else if(_controller.offset >= DEFAULT_SHOW_TOP_BTN && !showToTopBtn){
setState(() {
showToTopBtn = true;
});
}
复制代码
点击 floatingActionButton
返回到顶部:
_controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.ease);
复制代码
完整代码请参考下方GitHub项目中
/demo/scroller_demo.dart
文件。
在 NotificationListener 实例中布局基本上和 ScrollController 一致,不一样的地方在于 ListView 须要包裹在 NotificationListener 中做为 child,而后 NotificationListener 在 onNotification 中判断滚动偏移量:
if (notification is ScrollUpdateNotification && notification.depth == 0) {
double t = notification.metrics.pixels / DEFAULT_SCROLLER;
if (t < 0.0) {
t = 0.0;
} else if (t > 1.0) {
t = 1.0;
}
setState(() {
toolbarOpacity = t;
});
print(notification.metrics.pixels); //打印滚动位置
}
复制代码
完整代码请参考下方GitHub项目中
/demo/notification_listener_demo.dart
文件
完整代码奉上GitHub地址:fluter_demo ,欢迎star和fork。
到此,本文就结束了,若有不当之处敬请指正,一块儿学习探讨,谢谢🙏。