相信大家对于这种需求肯定不陌生:
侧滑出菜单,在Flutter 当中,这种需求怎么实现?
老套路,先分析一下需求:
1. 首先可以滑出菜单 2. 菜单滑动到一定距离完全滑出,未达到距离回滚 3. 菜单数量、样式随意定制 4. 菜单点击回调 5. 菜单展开时,点击 item 收回菜单(见QQ)
需求明了以后就可以写代码了。
最基本的,菜单要能滑的出来,我们思考一下,如何能在屏幕外面放置 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 :
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
。
完整的代码如下:
其中 behavior 有三个值:
• deferToChild:子组件会一个接一个的进行命中测试,如果子组件中有测试通过的,则当前组件通过,这就意味着,如果指针事件作用于子组件上时,其父级组件也肯定可以收到该事件。 • opaque:在命中测试时,将当前组件当成不透明处理(即使本身是透明的),最终的效果相当于当前Widget的整个区域都是点击区域。 • translucent:当点击组件透明区域时,可以对自身边界内及底部可视区域都进行命中测试,这意味着点击顶部组件透明区域时,顶部组件和底部组件都可以接收到事件。
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
if(_controller.offset != 0){
dismiss();
}else{
onTap();
}
},
child: SizedBox(
width: screenWidth,
child: child,
),
)
引用群里一大佬的话:不要把问题复杂化。
其实对于这种效果,我们仔细的想一分钟,几乎都能想出来解决方案的。而且实现起来也很简单。本来想封装成一个 ListView 的,后来感觉没什么必要,单独封装成一个 Item 也足够用了。
熟练使用导航栏,对于网站排版非常重要,使用CSS,js,jq等你可以转换成好看的导航栏而不是枯燥的HTML菜单。
做权限管理,一个核心思想就是后端做权限控制,前端做的所有工作都只是为了提高用户体验,我们不能依靠前端展示或者隐藏一个按钮来实现权限控制,这样肯定是不安全的。就像用户注册时需要输入邮箱地址,前端校验之后,后端还是要校验
数据结构中含有图片、名称、children的树形结构,需要展示出每一级的图片名称和图片,找了些树形图的插件,都没有展示大的图片的,一般都是小图标,就自己试着写一个包含图的简单的插件。
在pc 管理系统这种类型的产品,通常会涉及到账号权限的控制,不同的账号权限能浏览的功能模块是不同的,对应侧边栏菜单模块的显示也会不同。单点登录类系统,通常会多个项目公用一套登录系统,项目首页直接就是dashboard 或者 index页面
这是一个相当炫的功能,让网页看起来像桌面程序,如window的开始菜单。实现原理基本和纯CSS相册差不多,但要注意的事项比较多,让我们一步步来吧。
vue-contextmenujs:Vue 原生实现右键菜单组件, 零依赖;npm 安装npm install vue-contextmenujs;测试中使用的是element-ui图标 ;
Vue+Element 实现管理页面菜单栏, 点击菜单时 router 改变 hash 访问不同子组件。但是改变 hash 时菜单栏展开状态和 highlight 并不会同步, 需要手动实现。
本文分别使用 SFC(模板方式)和 tsx 方式对 Element Plus el-menu 组件进行二次封装,实现配置化的菜单,有了配置化的菜单,后续便可以根据路由动态渲染菜单。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!