
Flutter自定义View——仿高德三级联动Drawer
前言
我一直觉得高德地图主页上的Drawer滑动得非常漂亮,并且具有某种科技感。 我以前使用Android做过,最近不忙时又用Flutter做过一次。
示意图
为了方便区分布局结构,我使用了不同的颜色
Drawer高度状态
可以看到drawer 高度有三种情况:
最大高度
距离顶部有一小段空间,这里空间高度定位70,
drawer的高度为:屏幕高度-70
中等高度
这里我们将drawer的显示高度定位300
最小高度
这里drawer的显示高度定位150
Drawer的ui 结构
可以看到drawer内部的ui分为三块:
搜索区域、多功能区域、扩展区域
同时drawer在最大高度和中等高度之间滚动时,多功能区域需要缩进/展开 到 扩展区域
代码实现
基本布局
因为窗口最底层需要显示地图,同时drawer要显示不同的高度,所以这里我采用stack作为跟布局:
size由mediaQuery.of(context)获得
我们通过positioned包裹drawer,然后通过top来控制drawer上下移动的高度,为了捕获触摸事件,我们需要用GestureDetector对我们的drawer进行包裹,代码:
至此整个UI布局就搞定了,接下来处理手势滑动。
手势处理
首先我们只需要处理垂直滑动,因此在回调中,我们实现这三个方法:
dragStart
当手指触摸屏幕时,我们需要记录下点击位置:
dragUpdate
之后在用户滑动时,我们刷新drawer的position的top值(即initPositionTop),以此来达到drawer的滑动效果。
如果只是简单的滑动,我们可以直接将initPositionTop加上滑动差值即可,但是根据经验判断,后面肯定会需要滑动方向,所以我在这里顺便把滑动的方向也记录下来,这个可以根据滑动差值的正负来判断:
dragEnd
这里我们什么都不需要做,代码如下:
void verticalDragEnd(DragEndDetails details){
}
这时我们运行发现,drawer可以跟着手指的滑动表现收起/展开的效果,但是我们的手指离屏后,drawer也就停在那了(原始版抽屉)。
参见高德,可以看到抽屉始终会停留在三级状态中的一级,如果手指滑动超出界限/未到界限,抽屉会自动滚动/滚回到最近的等级高度,现在我们要进行升级了。
升级
准备工作
首先我们要记录一下三个高度对应的position的top值(drawer的实时top值以后就叫initPositionTop了):
然后我们需要记录一下drawer的状态:
分别对应top1,top2,top3
当我们滑动时,如果从top1滑向top2,但是未到top2的高度,就松手了,这时我们需要完成剩下的操作,这就用到了
AnimationController
Animation
animationController = AnimationController(vsync: this,duration: Duration(milliseconds: 300));
具体应该滑回top1,还是滑向top2呢?这里我们需要定两个阈值:
升级 dragStart
现在我们开始对原有的方法升级
当用户触摸时,我们先要确定drawer的初始状态:
升级dragEnd
在用户手指离开屏幕时,我们就要进行处理了,即:drawer是继续滚动,还是复位。
这个方法较长,我将说明写在注释里
在补全滑动这里,我们交给animationController来处理:
///begin基本是手指离屏的位置,end则是目标等级的top值
在动画的listener中,我们刷新initPositionTop的值:
至此我们就相对完善的完成了drawer的滑动功能。
多功能widget 显隐效果
继续观察drawer内部的widget,我们可以看到在top1和top2之间滚动时,内部的多功能区域也会进行相应的缩进和伸出,接下来我们实现这个。
UI布局
因为我们只需要移动扩展区域,就可以实现多功能区的滑出/收起 效果,所以我们可以用stack来完成基本的布局:
搜索区和多功能区,只需要调整top,使他们顺序排列即可。
而扩展区,我们需要在页面初始是遮住一部分多功能区(只漏出一行圆)。
方便起见,将多功能的高度定位 rowH * 3;
那么扩展区的top初始值就是多功能的top + rowH,这里我们给扩展区的top值定义一个变量:
expandPosTop = 多功能区的top + rowH
进而,我们可以确定,expandPosTop的变化范围是:
我们给这个变化值定义一个变量:topArea
topArea = [0 - rowH * 2];
最终扩展区的代码如下:
整体UI布局就完成了,我们接着实现滚动功能。
扩展区滑动
我们在dragUpdate和动画的listener中见到过这个方法:
refreshExpandWidgetTop();//这里就是实现对应功能的
这里我把说明写在注释里,方便阅读
当我们在调用上述方法外面刷新时,就会看到多功能区域的收起/伸出的效果了(给加点阴影会更好看),至此我们整个功能就实现了,如果对你有帮助点歌赞或和star吧。 :)
作者:吉哈达
来源:掘金
