Flutter开发超简单仿微信QQ侧滑菜单组件

2019-09-2020:21:23APP与小程序开发Comments2,909 views字数 3756阅读模式

这种需求肯定不陌生:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

Flutter开发超简单仿微信QQ侧滑菜单组件

侧滑出菜单,在Flutter 当中,这种需求怎么实现?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

看一下实现的效果:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

Flutter开发超简单仿微信QQ侧滑菜单组件

需求分析

老套路,先分析一下需求:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

  1. 首先可以滑出菜单
  2. 菜单滑动到一定距离完全滑出,未达到距离回滚
  3. 菜单数量、样式随意定制
  4. 菜单点击回调
  5. 菜单展开时,点击 item 收回菜单(见QQ)

代码实现

需求明了以后就可以写代码了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

1. 首先可以滑出菜单

最基本的,菜单要能滑的出来,我们思考一下,如何能在屏幕外面放置 Widget,并且还能滑动?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

基本上不到一分钟,相信大家都能想出来答案:ScrollView,没错,也就只有 ScrollView 满足我们的需求。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

说干就干:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

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 充满横向屏幕,这样后续的菜单就在屏幕外了

2. 菜单滑动到一定距离完全滑出,未达到距离回滚

这个效果就需要监听滑动距离和手势了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

如果滑动距离大于所有 menu 宽度的 1/4,那就全都滑出来,如果不到的话,就回滚回去。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

那就要判断一下手是否已经离开屏幕,在这个时候判断距离。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

本来想着套一个 Gesture,但是发现不行,问了一下大佬们,用了 Listener文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

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 方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

3. 菜单数量、样式随意定制

这个其实很简单,让「用户」来传入就好了,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

我只需要控制 menu 的宽度。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

于是我把 Container 的参数都扒了下来,封装成了一个组件 SlideMenuItem文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

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」 是自己写的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

4. 菜单点击回调

这里有个小问题:把 Menu 单独封装成了一个组件,那如何在点击 menu 的时候把 menu 收回去?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

基于这个问题,在创建整个 SlideItem 的时候,通过构造函数把每一个 menu 都添加上了 GestureDetector,然后在 onTap() 回调中调用 menu 的 onTap() 方法,再调用 dismiss() 方法来收回 menu。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

addAll(menu
       .map((w) => GestureDetector(
         child: w,
         onTap: (){
           w.onTap();
           dismiss();
         },
       ))
       .toList());
复制代码

5. 菜单展开时,点击 item 收回菜单

也就是 菜单展开时,点击了 item 的话,要先收回菜单。QQ 就是如此。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

这里有一个知识点,我们设置的点击事件默认是不会命中透明组件的,所以要给第一个默认占满屏幕宽度的 Widget 加上一个属性:behavior: HitTestBehavior.opaque文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

完整的代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

GestureDetector(
  behavior: HitTestBehavior.opaque,
  onTap: (){
    if(_controller.offset != 0){
      dismiss();
    }else{
      onTap();
    }
  },
  child: SizedBox(
    width: screenWidth,
    child: child,
  ),
)
复制代码

其中 behavior 有三个值:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

  • deferToChild:子组件会一个接一个的进行命中测试,如果子组件中有测试通过的,则当前组件通过,这就意味着,如果指针事件作用于子组件上时,其父级组件也肯定可以收到该事件。
  • opaque:在命中测试时,将当前组件当成不透明处理(即使本身是透明的),最终的效果相当于当前Widget的整个区域都是点击区域。
  • translucent:当点击组件透明区域时,可以对自身边界内及底部可视区域都进行命中测试,这意味着点击顶部组件透明区域时,顶部组件和底部组件都可以接收到事件。

总结

引用群里一大佬的话:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

不要把问题复杂化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

其实对于这种效果,我们仔细的想一分钟,几乎都能想出来解决方案的。而且实现起来也很简单。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

本来想封装成一个 ListView 的,后来感觉没什么必要,单独封装成一个 Item 也足够用了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

作者:Flutter笔记
链接:https://juejin.im/post/5d82130ce51d453b373b4dc6
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16615.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/xcx/16615.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定