相信你们对于这种需求确定不陌生:git
侧滑出菜单,在Flutter 当中,这种需求怎么实现?github
看一下实现的效果:微信
老套路,先分析一下需求:less
需求明了之后就能够写代码了。ide
最基本的,菜单要能滑的出来,咱们思考一下,如何能在屏幕外面放置 Widget,而且还能滑动?函数
基本上不到一分钟,相信你们都能想出来答案:ScrollView,没错,也就只有 ScrollView 知足咱们的需求。测试
说干就干:ui
SingleChildScrollView(
physics: ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: _controller,
child: Row(children: children),
)
---------------
// 第一个 Widget,宽度为屏幕的宽度
SizedBox(
width: screenWidth,
child: child,
),
复制代码
这个效果就须要监听滑动距离和手势了。this
若是滑动距离大于全部 menu 宽度的 1/4,那就全都滑出来,若是不到的话,就回滚回去。spa
那就要判断一下手是否已经离开屏幕,在这个时候判断距离。
原本想着套一个 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
:
class SlideMenuItem extends StatelessWidget {
SlideMenuItem({
Key key,
@required this.child,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
this.height,
BoxConstraints constraints,
this.margin,
this.transform,
@required this.onTap,
}) : assert(child != null),
assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()),
assert(
color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'),
decoration =
decoration ?? (color != null ? BoxDecoration(color: color) : null),
constraints = (height != null)
? constraints?.tighten(height: height) ??
BoxConstraints.tightFor(height: height)
: constraints,
super(key: key);
final BoxConstraints constraints;
final Decoration decoration;
final AlignmentGeometry alignment;
final EdgeInsets padding;
final Decoration foregroundDecoration;
final EdgeInsets margin;
final Matrix4 transform;
final Widget child;
final double height;
final GestureTapCallback onTap;
@override
Widget build(BuildContext context) {
return Container(
child: child,
alignment: alignment,
constraints: constraints,
decoration: decoration,
padding: padding,
width: screenWidth / 5,
height: height,
foregroundDecoration: foregroundDecoration,
margin: margin,
transform: transform,
);
}
}
复制代码
这么长的代码,其实就 「width」 和 「onTap」 是本身写的。
这里有个小问题:把 Menu 单独封装成了一个组件,那如何在点击 menu 的时候把 menu 收回去?
基于这个问题,在建立整个 SlideItem
的时候,经过构造函数把每个 menu 都添加上了 GestureDetector
,而后在 onTap() 回调中调用 menu 的 onTap() 方法,再调用 dismiss()
方法来收回 menu。
代码以下:
addAll(menu
.map((w) => GestureDetector(
child: w,
onTap: (){
w.onTap();
dismiss();
},
))
.toList());
复制代码
也就是 菜单展开时,点击了 item 的话,要先收回菜单。QQ 就是如此。
这里有一个知识点,咱们设置的点击事件默认是不会命中透明组件的,因此要给第一个默认占满屏幕宽度的 Widget 加上一个属性:behavior: HitTestBehavior.opaque
。
完整的代码以下:
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
if(_controller.offset != 0){
dismiss();
}else{
onTap();
}
},
child: SizedBox(
width: screenWidth,
child: child,
),
)
复制代码
其中 behavior 有三个值:
引用群里一大佬的话:
不要把问题复杂化。
其实对于这种效果,咱们仔细的想一分钟,几乎都能想出来解决方案的。并且实现起来也很简单。
原本想封装成一个 ListView 的,后来感受没什么必要,单独封装成一个 Item 也足够用了。
代码已传到GitHub:github.com/wanglu1209/…
另我我的建立了一个「Flutter 交流群」,能够添加我我的微信 「17610912320」来入群。
推荐阅读:
Flutter | 1.9 全新组件 ToggleButtons
Flutter | WReorderList 一个能够指定两个item互换位置的组件
Flutter | 如何实现一个水波纹扩散效果的 Widget