美文网首页
Flutter-仿QQ侧滑Item菜单栏实现

Flutter-仿QQ侧滑Item菜单栏实现

作者: Cosecant | 来源:发表于2023-05-25 11:43 被阅读0次

    不看功夫,先看看效果:

    效果图,为啥GIF不动呢

    实现以上效果,主要是通过GestureDetector检测用户手势来处理的。通过Transform.translation调整X坐标进行处理,原理十分简单。

    大致代码内容如下:
    2023-06-05
    增加滑动后自动关闭的动画效果

    import 'package:flutter/material.dart';
    
    class SlideMenuView extends StatefulWidget {
      const SlideMenuView(
          {super.key,
          this.maxDragDistance = 80.0,
          required this.child,
          this.background,
          required this.onDragStart});
    
      final double maxDragDistance;
    
      final Widget child;
    
      final Widget? background;
    
      final void Function() onDragStart;
    
      @override
      State<SlideMenuView> createState() => SlideMenuViewState();
    }
    
    class SlideMenuViewState extends State<SlideMenuView>
        with TickerProviderStateMixin {
      double _dragDistance = 0;
    
      late AnimationController _animController;
    
      Animation<double>? _animation;
    
      @override
      void initState() {
        _animController = AnimationController(
            duration: const Duration(milliseconds: 200), vsync: this)
          ..addListener(() {
            if (_animation == null) return;
            Future.microtask(
                () => setState(() => _dragDistance = _animation!.value));
            debugPrint("===> 动画运行中:${_animation!.value}");
          });
        super.initState();
      }
    
      void _updateDragDataAndUI(double moveDistance) {
        debugPrint("===> 拖动进行事件");
        _dragDistance += moveDistance;
        if (_dragDistance < -widget.maxDragDistance) {
          _dragDistance = -widget.maxDragDistance;
        } else if (_dragDistance >= 0.0) {
          _dragDistance = 0.0;
        }
        Future.microtask(() => setState(() {}));
      }
    
      void _dragCompleted() {
        debugPrint("===> 拖动完成事件");
        _animController.reset(); //动画重置
        if (_dragDistance < -widget.maxDragDistance / 2.0) {
          _animation = _animController
              .drive(Tween(begin: _dragDistance, end: -widget.maxDragDistance));
        } else if (_dragDistance > -widget.maxDragDistance / 2.0) {
          _animation = _animController.drive(Tween(begin: _dragDistance, end: 0.0));
        }
        Future.microtask(_animController.forward);
      }
    
      void closeSlideMenu() => setState(() => _dragDistance = 0);
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
            onHorizontalDragDown: (_) => widget.onDragStart(),
            onHorizontalDragStart: (_) => widget.onDragStart(),
            onHorizontalDragUpdate: (details) =>
                _updateDragDataAndUI(details.delta.dx),
            onHorizontalDragCancel: _dragCompleted,
            onHorizontalDragEnd: (_) => _dragCompleted(),
            child: Stack(children: [
              if (widget.background != null) widget.background!,
              Transform.translate(
                  offset: Offset(_dragDistance, 0), child: widget.child)
            ]));
      }
    }
    
    extension SlideMenuViewExtensions on Widget {
      /// 应用侧滑菜单的效果
      Widget applySlideMenuEffect(
          {Key? key,
          double maxSlideDistance = 80.0,
          Widget? background,
          required void Function() onDragStart}) {
        return SlideMenuView(
            key: key,
            maxDragDistance: maxSlideDistance,
            onDragStart: onDragStart,
            background: background,
            child: this);
      }
    }
    

    2023-05-26

    import 'package:flutter/material.dart';
    
    class SlideMenuView extends StatefulWidget {
      const SlideMenuView(
          {super.key,
          this.maxDragDistance = 80.0,
          required this.child,
          this.background,
          required this.onDragStart});
    
      final double maxDragDistance;
    
      final Widget child;
    
      final Widget? background;
    
      final void Function() onDragStart;
    
      @override
      State<SlideMenuView> createState() => SlideMenuViewState();
    }
    
    class SlideMenuViewState extends State<SlideMenuView> {
      double _dragDistance = 0;
    
      void _updateDragDataAndUI(double moveDistance) {
        _dragDistance += moveDistance;
        if (_dragDistance <= -widget.maxDragDistance) {
          _dragDistance = -widget.maxDragDistance;
        } else if (_dragDistance >= 0.0) {
          _dragDistance = 0.0;
        }
        Future.microtask(() => setState(() {}));
      }
    
      void _dragCompleted() {
        if (_dragDistance < -widget.maxDragDistance / 2.0) {
          _dragDistance = -widget.maxDragDistance;
        } else if (_dragDistance > -widget.maxDragDistance / 2.0) {
          _dragDistance = 0.0;
        }
        Future.microtask(() => setState(() {}));
      }
    
      void closeSlideMenu() => setState(() => _dragDistance = 0);
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
            onHorizontalDragDown: (_) => widget.onDragStart(),
            onHorizontalDragStart: (_) => widget.onDragStart(),
            onHorizontalDragUpdate: (details) =>
                _updateDragDataAndUI(details.delta.dx),
            onHorizontalDragCancel: _dragCompleted,
            onHorizontalDragEnd: (_) => _dragCompleted(),
            child: Stack(children: [
              if (widget.background != null) widget.background!,
              Transform.translate(
                  offset: Offset(_dragDistance, 0), child: widget.child)
            ]));
      }
    }
    
    /// 提供扩展类,让Widget直接使用视图
    extension SlideMenuViewExtensions on Widget {
      Widget applySlideMenuEffect(
          {Key? key,
          double maxSlideDistance = 80.0,
          Widget? background,
          required void Function() onDragStart}) {
        return SlideMenuView(
            key: key,
            maxDragDistance: maxSlideDistance,
            onDragStart: onDragStart,
            background: background,
            child: this);
      }
    }
    

    应用案例

    Container(
                      height: 80,
                      width: double.infinity,
                      color: Colors.red,
                      child: Text(widget.title))
                  .applySlideMenuEffect(
                      onDragStart: () {},
                      background: Container(
                          width: double.infinity,
                          alignment: Alignment.centerRight,
                          child: const Text('删除')))
    

    关闭菜单通过调用GloablKey来调用 closeSlideMenu方法。

    *原创内容,转载和分享请注明作者或出处!

    相关文章

      网友评论

          本文标题:Flutter-仿QQ侧滑Item菜单栏实现

          本文链接:https://www.haomeiwen.com/subject/znlhedtx.html