Code前端首页关于Code前端联系我们

Flutter正在开发一个超级简单的模仿微信QQ的侧滑菜单组件

terry 2年前 (2023-09-23) 阅读数 68 #移动小程序

这个需求肯定很熟悉:Flutter开发超简单仿微信QQ侧滑菜单组件

推出菜单。这个需求在Flutter中如何实现呢?

看一下实现的效果:Flutter开发超简单仿微信QQ侧滑菜单组件

需求分析

老套路,先分析需求:

  1. 首先可以滑出菜单
  2. 菜单滑出一定距离,滑动完全向外滚动,到达距离前向后滚动
  3. 菜单数量和样式可随意定制
  4. 菜单点击回调
  5. 菜单展开时,点击点恢复菜单(见QQ)

代码实现

需求明确后,就可以编写代码了。

1。首先可以将菜单滑出

最基本的是菜单必须能够滑出。我们想一下,如何才能将小部件放置在屏幕之外并仍然推送它呢?

基本上不到一分钟的时间,我想大家都能想出答案: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. 设置滑动距离控制器来监听设置child到 Row ,第一个 Widget 填满横屏,因此后续菜单位于屏幕之外

2。菜单滑动到一定距离后一直滑出,到达距离之前回滚

这个效果需要监控滑动距离和动作。

如果滑动距离大于所有菜单宽度的1/4,则将其全部推出。如果没有,则回滚。

然后判断你的手是否已经离开屏幕并判断此时的距离。

本来想用手势但发现不行。我请教了专家,用了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方法。

3。菜单的数量和样式可以随意定制

这个其实很简单,让“用户”提交即可。

我只需要控制菜单的宽度。

于是我把容器的参数全部去掉,包裹在一个组件中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,
    );
  }
}
复制代码

这么长的代码,其实是我自己写的“在宽度上”。?

基于这个问题,当你创建整个SlideItem时,每个菜单都会添加到GestureDetector,然后调用T菜单的(back)()方法,然后调用dismiss()方法恢复菜单。

代码如下:

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

5。菜单展开时,点击该点即可收回菜单

即菜单展开时,点击该点,先收回菜单。 QQ就这样了。

这里有一个知识点。我们设置的点击事件默认不会命中透明组件,所以我们需要给第一个默认全屏宽度的widget添加一个属性:,行为:HitTestBehavior.opaque♼♼。

完整代码如下:

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

Behavior 有三个值:

  • deferToChild:子组件将一一进行命中测试。如果子组件中的测试通过,则当前组件也通过,这意味着如果指针事件作用于子组件,则其父组件肯定会收到该事件。
  • 不透明:在命中测试期间,当前组件被视为不透明(即使它是透明的)。最终的效果相当于当前widget的整个区域都是点击区域。
  • 半透明:当点击组件的透明区域时,既可以对其自身边界内的可见区域进行命中测试,也可以对底部进行命中测试。这意味着当顶部组件的透明区域被点击时,顶部组件和底部组件都可以接收到事件。

概述

引用群里一位大佬的话:

别把问题复杂化了。

实际上,如果我们认真思考一下这个效果,我们几乎可以想出一个解决方案。而且很容易实现。

本来想用ListView包裹起来,后来觉得没有必要。只需将其封装在一个物品中就足够了。

作者:Flutter Notes
链接:https://juejin.im/post/5d82130ce51d453b373b4dc6
来源:掘金❀属于作者所有。商业转载请联系作者获取授权。非商业转载请注明出处。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门