前言
最近工作繁忙,下班后都不想开电脑😂,现在工作告一段落了,终于可以抽出时间来继续写这个系列。项目实现请看这篇文章,今天来讲讲项目实现后的第二个棘手问题,粘性头部无法像头部组件一样滑动。
项目地址
问题描述
话不多说,先上图看看问题是个什么情况:
当Header成为粘性头部后,无法滑动。
问题分析
Header组件的本质是滚动组件的Item,所以Header组件不需要进行额外处理就能响应滑动事件。Header成为粘性头部后,会在Header组件上方显示粘性头部组件(关于这方面的具体详情请看项目实现),这时候尝试滑动的其实是粘性头部组件,而粘性头部组件没有做额外处理,所以无法响应滑动事件。
解决方案
问题已经明确,那我们开始着手加上滑动事件处理。Flutter中要添加各种手势事件监听回调,那GestureDetector
组件肯定是少不了的。
GestureDetector
构造方法中一堆手势事件回调就不一一展示了,这里主要用到了onVerticalDragUpdate
(垂直方向滑动中)和onVerticalDragEnd
(垂直方向滑动结束)这两个回调。
滑动粘性头部组件时,只需要在onVerticalDragUpdate
回调中同步改变滚动组件的滚动位置,便能实现像头部组件一样的滑动效果。
onVerticalDragUpdate
回调处理源码:
void _onVerticalDragUpdate(DragUpdateDetails details) {
// currentPixels: 当前滚动位置
// details.delta.dy: 滑动变化量
widget.controller.scrollPosition?.jumpTo(widget.controller.currentPixels + details.delta.dy);
}
滑动结束时,如果有一定的滑动速度,那么滑动不会马上停止,如果触及到边界,不同的平台还会有不同的效果,例如iOS平台是回弹效果。看到这,我猜你已经联想到了滚动组件的physics
属性,不设置这个属性的情况下,Android平台默认使用ClampingScrollPhysics
,iOS平台默认使用BouncingScrollPhysics
。
那这些跟onVerticalDragEnd
的回调处理又有什么关系呢?如果只考虑将滑动速度慢慢减小直到滑动停止,那么通过AnimationController
创建动画就能实现,但是这有以下两个问题:
AnimationController
默认创建的动画是线性动画,虽然可以通过CurvedAnimation
转为非线性动画,但是这个动画曲线不一定和滚动组件的动画曲线一致,导致用户体验不一致- 滚动位置超出边界时,不同的平台或设置的
physics
属性不同会有不同的处理方式,简单的动画曲线无法满足该需求
要解决以上两个问题,最好的办法是模仿滚动组件的滑动事件处理。对于滚动组件,滑动结束后的动画是由Simulation
对象决定的,而Simulation
是通过createBallisticSimulation
方法创建的,createBallisticSimulation
是ScrollPhysics
类方法,ClampingScrollPhysics
和BouncingScrollPhysics
都是ScrollPhysics
的子类。
现在首先是要获取到ScrollPhysics
对象,这个对于已有项目来说很简单,只需要通过scrollPosition
就能获取。创建Simulation
对象的源码如下:
// 手指离开屏幕时的滑动速度
var velocity = details.velocity.pixelsPerSecond;
// 创建Simulation
var simulation = scrollPosition.physics.createBallisticSimulation(scrollPosition, velocity);
Simulation
对象有了,那该怎么变成动画呢?这里就需要前面提到的AnimationController
,通过AnimationController
的animateWith
方法将Simulation
对象转为动画曲线。通过监听动画改变,不断调整滚动组件的滚动位置实现动画效果。监听动画改变的源码如下:
_animationController.addListener(() {
widget.controller.scrollPosition?.jumpTo(_animationController.value);
});
onVerticalDragEnd
回调处理源码:
void _onVerticalDragEnd(DragEndDetails details) {
var scrollPosition = widget.controller.scrollPosition;
if (scrollPosition != null) {
// 手指离开屏幕时的滑动速度
var velocity = details.velocity.pixelsPerSecond;
// 创建Simulation
var simulation = scrollPosition.physics
.createBallisticSimulation(scrollPosition, velocity);
if (simulation != null) {
_animationController.animateWith(simulation);
}
}
}
最终效果展示:
总结
这个系列写到这基本算是告一段落了,项目实现中更细节的功能,例如支持水平滚动组件、反向滚动组件等就不再赘述,欢迎查看项目源码。
关于项目的后续计划,短期内会对项目进行一次重构,这是为了提升项目的扩展性,同时会完善一些文档说明,以及支持普通Item的跳转。项目已经趋于稳定,所以长期来看不会有特别大的改动,只会不断完善一些常见实例,例如微信通讯录、城市列表等。当然,如果你发现现有功能满足不了你的需求,欢迎评论留言提issue。
后面会不定期更新新版本使用说明以及常见实例介绍等文章,如果你有兴趣了解的话,欢迎查看后续文章。
最后
如果这个项目对你有所帮助,点赞👍加星🌟安排一下~
如果你发现了bug或想要新功能,欢迎在issue留言讨论,同时也欢迎你加入到这个项目中,为这项目出一份力。
系列文章
Flutter - 一个易用且功能强大的粘性头部组件库,适用于任何支持滚动的组件(一)
Flutter - 一个易用且功能强大的粘性头部组件库,适用于任何支持滚动的组件(二)
Flutter - 一个易用且功能强大的粘性头部组件库,适用于任何支持滚动的组件(三)
Flutter - 一个易用且功能强大的粘性头部组件库,适用于任何支持滚动的组件(四)
Flutter - 一个易用且功能强大的粘性头部组件库,适用于任何支持滚动的组件(五)