Flutter 可拖拽Widget

作者: 旺仔_100 | 来源:发表于2021-01-08 19:06 被阅读0次

    一、背景
    我们经常会看到管理手机软件那种悬浮小圆圈,或者微信公众号的悬浮按钮。它们不仅能悬浮在所有页面之上,还可以在界面任意拖动。我们使用flutter 改如何实现呢?

    二、思路分析
    1.全局弹窗。这个在flutter里面有一个 Overlay.of(context).insert(overlayEntry);
    这个就是可以全局浮动的弹窗。
    2.任意拖动。刚好flutter有个Draggable控件,它可以直接拖动一个widget。但是它一松手就会回到之前的位置。
    3.为了Draggable控件停留在我们想要的位置,那么久引入了DragTarget。

    三、每一个Widget介绍
    1.Overlay:Overlay 之于 Flutter , 有点相当于 KeyWindow 之于 iOS 一样,可以将子 widget 置于其他 widget 的顶层,带来 “悬浮”的效果。
    2.OverlayEntry:OverlayEntry 之于 Overlay,对于 iOS 开发而言,又有点 subView 之于 KeyWindow 的味道了。 OverlayEntry 是视图的实际的容器, 把其往 Overlay 那儿添加了,就可以成像了。
    3.Draggable

    const Draggable({
     Key key,
     @required this.child,              // 初始化显示的 widget
     @required this.feedback,       // 拖拽过程中(活动中)显示的 widget
     this.data,                                 // widget 携带的数据,放手时可以将这个 data 数据传递出去
     this.axis,                                 // 限制 draggable 的移动范围
     this.childWhenDragging,            // 拖住动作发生过程中,初始化位置显示的 widget
     this.feedbackOffset = Offset.zero, // 当 feedback 与 child 相比,有 transform 的时候,需要用到这个属性来调整 hittest 范围
     this.dragAnchor = DragAnchor.child, //锚点
     this.affinity,                         // 单词的意思是亲和力,当 Draggable 位于 另外一个 Scrollable 控件內时,来控制到底这个这个拖拽事件到底由 Draggable 响应,还是由 Scrollable 控件来响应
     this.maxSimultaneousDrags, // 限制有多少个 Draggable 同时发生 拖拽动作
     this.onDragStarted,                    // 拖拽动作开始回调
     this.onDraggableCanceled,      // 拖拽动作取消回调
     this.onDragEnd,                            //拖拽动作结束回调
     this.onDragCompleted,              // 拖拽动作完成回调, 并被一个 DragTarget 接收
     this.ignoringFeedbackSemantics = true, // 也是看了文档才知道,这个属性还是有点用的,当 feedback 跟 child 是同一个 widget A 对象时,就应该把这个属性设成 false, 配合赋值一个 GlobalKey,这样,这个 widget A 就不会在 feedback 跟 child 切换时,重新销毁后又创建了。这个在 widget A 带有播放动画是比较容易看出区别,每次手指拖放都伴随着动画的重新开始
    })
    

    4.DragTarget

    const DragTarget({
      Key key,
      @required this.builder,  //根据 Draggable 传过来的 data ,来显示想要的 widget
      this.onWillAccept,            // 根据传过来的 data ,选择是否接收这个 Draggable, 返回 true 则激活 onAccept
      this.onAccept,                    // Draggable 被丢进了这个 DragTarget 区域后回调
      this.onLeave,                     // Draggable 离开 DragTarget 区域后的回调
    }) : super(key: key);
    

    四、完整代码

    import 'package:flutter/cupertino.dart';
    
    class DragOverlay {
      static Widget view;
      static OverlayEntry _holder;
    
      static void remove() {
        if (_holder != null) {
          _holder.remove();
          _holder = null;
        }
      }
    
      static void show({@required BuildContext context, @required Widget view}) {
        DragOverlay.view = view;
        remove();
        OverlayEntry overlayEntry = OverlayEntry(builder: (context){
          return Positioned(
            top: MediaQuery.of(context).size.height *0.7,
            child: _buildDraggable(context),
          );
        });
        Overlay.of(context).insert(overlayEntry);
        _holder = overlayEntry;
      }
    
      static _buildDraggable(context){
        return Draggable(
          child: view,
          feedback: view,
          onDragStarted: (){
    
          },
          onDragEnd: (detail){
            print("onDraEnd:${detail.offset}");
            //放手时候创建一个DragTarget
            createDragTarget(offset:detail.offset,context:context);
          },
          //当拖拽的时候就展示空
          childWhenDragging: Container(),
          ignoringFeedbackSemantics: false,
        );
      }
    
      static void createDragTarget({Offset offset,BuildContext context}){
         if(_holder != null){
           _holder.remove();
         }
         _holder = new OverlayEntry(builder: (context){
           bool isLeft = true;
           if(offset.dx + 100 > MediaQuery.of(context).size.width / 2){
             isLeft = false;
           }
           double maxY = MediaQuery.of(context).size.height - 100;
    
           return Positioned(
             top: offset.dy < 50 ? 50 : offset.dy > maxY ? maxY : offset.dy,
             left: isLeft ? 0:null,
             right: isLeft ? null : 0,
             child: DragTarget(
               onWillAccept: (data){
                 print('onWillAccept:$data');
                 ///返回true 会将data数据添加到candidateData列表中,false时会将data添加到rejectData
                 return true;
               },
               onAccept: (data){
                 print('onAccept : $data');
               },
               onLeave: (data){
                 print("onLeave");
               },
               builder: (BuildContext context,List incoming,List rejected){
                 return _buildDraggable(context);
               },
             ),
           );
         });
         Overlay.of(context).insert(_holder);
      }
    }
    
    

    五、调用

    DragOverlay.show(context: context, view: Container(
          width: 100,
          height: 20,
          color: Colors.red,
        ));
    

    相关文章

      网友评论

        本文标题:Flutter 可拖拽Widget

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