侧滑出菜单,在Flutter当中,这种需求怎么实现?
看一下实现的效果:
老套路,先分析一下需求:
需求明了以后就可以写代码了。
最基本的,菜单要能滑的出来,我们思考一下,如何能在屏幕外面放置Widget,并且还能滑动?
基本上不到一分钟,相信大家都能想出来答案:ScrollView,没错,也就只有ScrollView满足我们的需求。
说干就干:
SingleChildScrollView(physics:ClampingScrollPhysics(),scrollDirection:Axis.horizontal,controller:_controller,child:Row(children:children),)---------------//第一个Widget,宽度为屏幕的宽度SizedBox(width:screenWidth,child:child,),1.首先把ScrollView滑动的位置改为横向2.把滑动的效果改为ClampingScrollPhysics,否则iOS会有回弹的效果3.设置一个controller,用于监听滑动距离4.设置child为Row,并且第一个Widget充满横向屏幕,这样后续的菜单就在屏幕外了
这个效果就需要监听滑动距离和手势了。
如果滑动距离大于所有menu宽度的1/4,那就全都滑出来,如果不到的话,就回滚回去。
那就要判断一下手是否已经离开屏幕,在这个时候判断距离。
本来想着套一个Gesture,但是发现不行,问了一下大佬们,用了Listener。
代码如下:
Listener(onPointerUp:(d){if(_controller.offset<(screenWidth/5)*menu.length/4){_controller.animateTo(0,duration:Duration(milliseconds:100),curve:Curves.linear);}else{_controller.animateTo(menu.length*(screenWidth/5),duration:Duration(milliseconds:100),curve:Curves.linear);}},child:SingleChildScrollView(physics:ClampingScrollPhysics(),scrollDirection:Axis.horizontal,controller:_controller,child:Row(children:children),),)很简单,就是在手抬起的时候判断了一下距离,然后调用了animteTo方法。
这个其实很简单,让「用户」来传入就好了,
我只需要控制menu的宽度。
于是我把Container的参数都扒了下来,封装成了一个组件SlideMenuItem:
这里有个小问题:把Menu单独封装成了一个组件,那如何在点击menu的时候把menu收回去?
基于这个问题,在创建整个SlideItem的时候,通过构造函数把每一个menu都添加上了GestureDetector,然后在onTap()回调中调用menu的onTap()方法,再调用dismiss()方法来收回menu。
这里有一个知识点,我们设置的点击事件默认是不会命中透明组件的,所以要给第一个默认占满屏幕宽度的Widget加上一个属性:behavior:HitTestBehavior.opaque。
完整的代码如下:
GestureDetector(behavior:HitTestBehavior.opaque,onTap:(){if(_controller.offset!=0){dismiss();}else{onTap();}},child:SizedBox(width:screenWidth,child:child,),)其中behavior有三个值:
deferToChild:子组件会一个接一个的进行命中测试,如果子组件中有测试通过的,则当前组件通过,这就意味着,如果指针事件作用于子组件上时,其父级组件也肯定可以收到该事件。opaque:在命中测试时,将当前组件当成不透明处理(即使本身是透明的),最终的效果相当于当前Widget的整个区域都是点击区域。translucent:当点击组件透明区域时,可以对自身边界内及底部可视区域都进行命中测试,这意味着点击顶部组件透明区域时,顶部组件和底部组件都可以接收到事件。
引用群里一大佬的话:
不要把问题复杂化。
其实对于这种效果,我们仔细的想一分钟,几乎都能想出来解决方案的。而且实现起来也很简单。
本来想封装成一个ListView的,后来感觉没什么必要,单独封装成一个Item也足够用了。