Flutter 可拖拽Widget

一、背景
我們經(jīng)常會看到管理手機軟件那種懸浮小圓圈,或者微信公眾號的懸浮按鈕。它們不僅能懸浮在所有頁面之上,還可以在界面任意拖動。我們使用flutter 改如何實現(xiàn)呢?

二、思路分析
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 開發(fā)而言,又有點 subView 之于 KeyWindow 的味道了。 OverlayEntry 是視圖的實際的容器, 把其往 Overlay 那兒添加了,就可以成像了。
3.Draggable

const Draggable({
 Key key,
 @required this.child,              // 初始化顯示的 widget
 @required this.feedback,       // 拖拽過程中(活動中)顯示的 widget
 this.data,                                 // widget 攜帶的數(shù)據(jù),放手時可以將這個 data 數(shù)據(jù)傳遞出去
 this.axis,                                 // 限制 draggable 的移動范圍
 this.childWhenDragging,            // 拖住動作發(fā)生過程中,初始化位置顯示的 widget
 this.feedbackOffset = Offset.zero, // 當 feedback 與 child 相比,有 transform 的時候,需要用到這個屬性來調(diào)整 hittest 范圍
 this.dragAnchor = DragAnchor.child, //錨點
 this.affinity,                         // 單詞的意思是親和力,當 Draggable 位于 另外一個 Scrollable 控件內(nèi)時,來控制到底這個這個拖拽事件到底由 Draggable 響應(yīng),還是由 Scrollable 控件來響應(yīng)
 this.maxSimultaneousDrags, // 限制有多少個 Draggable 同時發(fā)生 拖拽動作
 this.onDragStarted,                    // 拖拽動作開始回調(diào)
 this.onDraggableCanceled,      // 拖拽動作取消回調(diào)
 this.onDragEnd,                            //拖拽動作結(jié)束回調(diào)
 this.onDragCompleted,              // 拖拽動作完成回調(diào), 并被一個 DragTarget 接收
 this.ignoringFeedbackSemantics = true, // 也是看了文檔才知道,這個屬性還是有點用的,當 feedback 跟 child 是同一個 widget A 對象時,就應(yīng)該把這個屬性設(shè)成 false, 配合賦值一個 GlobalKey,這樣,這個 widget A 就不會在 feedback 跟 child 切換時,重新銷毀后又創(chuàng)建了。這個在 widget A 帶有播放動畫是比較容易看出區(qū)別,每次手指拖放都伴隨著動畫的重新開始
})

4.DragTarget

const DragTarget({
  Key key,
  @required this.builder,  //根據(jù) Draggable 傳過來的 data ,來顯示想要的 widget
  this.onWillAccept,            // 根據(jù)傳過來的 data ,選擇是否接收這個 Draggable, 返回 true 則激活 onAccept
  this.onAccept,                    // Draggable 被丟進了這個 DragTarget 區(qū)域后回調(diào)
  this.onLeave,                     // Draggable 離開 DragTarget 區(qū)域后的回調(diào)
}) : 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}");
        //放手時候創(chuàng)建一個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數(shù)據(jù)添加到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);
  }
}

五、調(diào)用

DragOverlay.show(context: context, view: Container(
      width: 100,
      height: 20,
      color: Colors.red,
    ));
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容