Flutter 史上最牛拖動控件 Draggable

很少說,咱們確定遇到過這個需求:git

把一個控件從當前位置移動到另外一個位置。可能需求最多的就像是支付寶應用頁面的編輯:github

好比,我想把最近使用的 紅包 添加到 個人應用 當中,支付寶這裏是用的 + 號。bash

那若是產品說我就想讓它拖過去,這可咋整?markdown

不慌,Flutter 也爲咱們提供了相關的 Widget。app

Draggable

Flutter 若是要實現這種效果,那麼非 Draggable 不可。函數

照例咱們查看官方文檔。ui

A widget that can be dragged from to a DragTarget.

可拖動到 DragTarget 的小部件。
複製代碼

那也就是說,除了 Draggable ,還有一個 DragTarget。this

DragTarget 是用來接收咱們拖過去的 Widget 的,咱們後面再說。spa

先說Draggable。指針

在官方文檔找了一圈沒發現Demo,那沒辦法了,直接開擼。

先看構造函數:

class Draggable<T> extends StatefulWidget {
  /// Creates a widget that can be dragged to a [DragTarget].
  ///
  /// The [child] and [feedback] arguments must not be null. If
  /// [maxSimultaneousDrags] is non-null, it must be non-negative.
  const Draggable({
    Key key,
    @required this.child,
    @required this.feedback,
    this.data,
    this.axis,
    this.childWhenDragging,
    this.feedbackOffset = Offset.zero,
    this.dragAnchor = DragAnchor.child,
    this.affinity,
    this.maxSimultaneousDrags,
    this.onDragStarted,
    this.onDraggableCanceled,
    this.onDragEnd,
    this.onDragCompleted,
    this.ignoringFeedbackSemantics = true,
  }) : assert(child != null),
       assert(feedback != null),
       assert(ignoringFeedbackSemantics != null),
       assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
       super(key: key);
}
複製代碼

能夠看到該類還支持泛型,這個泛型是來界定 data的,後面再說。

先來看看必須的參數,child 和 feedback。

child 應該不比多說,說一下 feedback。

點擊查看feedback 參數,上面的註釋這樣寫着:

當拖動正在進行時在指針下顯示的小部件。

既然搞懂了必傳參數,那就開擼:

Widget _createGridView(List<String> _items) {
  return GridView.builder(
    itemCount: _items.length,
    shrinkWrap: true, // 至關於Android的 wrap_content ,也就是包裹住該 widget的高度
    physics: NeverScrollableScrollPhysics(), // 不容許滑動
    padding: EdgeInsets.all(10),
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 5,  // 設置 gridview 每一行的數量
      mainAxisSpacing: 10,
      crossAxisSpacing: 10,
    ),
    itemBuilder: (context, index) {
      return Draggable( // 返回一個Draggable
        // 必需要一個Material,否則拖動時Text會有雙下劃線
        feedback: Material(
          child: Container(
            height: 100,
            width: 100,
            color: Colors.blueAccent,
            alignment: Alignment.center,
            child: Text(
              _items[index],
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
        child: Container(
          color: Colors.blueAccent,
          alignment: Alignment.center,
          child: Text(
            _items[index],
            style: TextStyle(color: Colors.white),
          ),
        ),
      );
    },
  );
}
複製代碼

咱們定義一個 GridView,裏面每個 item 都是一個 Draggable。

來運行一下看效果:

能夠看到咱們確實是能夠拖動了,大功已經告成一半了。

那麼咱們下面開始定義接收的部件 DragTarget。

DragTarget

廢話也很少說,直接看構造函數,看看什麼是必填:

class DragTarget<T> extends StatefulWidget {
  /// Creates a widget that receives drags.
  ///
  /// The [builder] argument must not be null.
  const DragTarget({
    Key key,
    @required this.builder,
    this.onWillAccept,
    this.onAccept,
    this.onLeave,
  }) : super(key: key);
}
複製代碼

能夠看到必傳的參數也就是一個builder,用來構建咱們的頁面使用。

其餘參數看名字也都能明白:

  • onWillAccept 拖到該控件上時調用
  • onAccept 放到該控件時調用
  • onLeave 沒有放到該控件時調用

那咱們這裏只須要一個確認已經放到該控件時的回調,來接收咱們傳過來的值。

擼碼:

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('DraggablePage'),
    ),
    body: Column(
      children: <Widget>[
        _createGridView(_items1),
        SizedBox(height: 100,),// 佔位
        DragTarget<String>(	// 用來接收數據的 Widget
          builder: (
            BuildContext context,
            List<dynamic> accepted,
            List<dynamic> rejected,
          ) {
            return _createGridView(_items2);
          },
          // 用來接收數據
          onAccept: (String data) {
            setState(() {
              _items2.add(data);
            });
          },
        )
      ],
    ),
  );
}
複製代碼

這是完整的 build 代碼,可是還須要改造一下咱們的 Draggable 。

再看一下Draggable的構造函數:

class Draggable<T> extends StatefulWidget {
  /// Creates a widget that can be dragged to a [DragTarget].
  ///
  /// The [child] and [feedback] arguments must not be null. If
  /// [maxSimultaneousDrags] is non-null, it must be non-negative.
  const Draggable({
    Key key,
    @required this.child,
    @required this.feedback,
    this.data,
    this.axis,
    this.childWhenDragging,
    this.feedbackOffset = Offset.zero,
    this.dragAnchor = DragAnchor.child,
    this.affinity,
    this.maxSimultaneousDrags,
    this.onDragStarted,
    this.onDraggableCanceled,
    this.onDragEnd,
    this.onDragCompleted,
    this.ignoringFeedbackSemantics = true,
  }) : assert(child != null),
       assert(feedback != null),
       assert(ignoringFeedbackSemantics != null),
       assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
       super(key: key);
}
複製代碼

由於若是想要傳入數據,那也就必需要有數據能夠傳,也就是咱們構造函數裏看到的 data 字段。

還須要刪除咱們的源數據,那也就是要監聽拖動結束的回調,這裏就是 onDragCompleted

咱們來看改造後的Draggable:

Widget _createGridView(List<String> _items) {
  return GridView.builder(
    itemCount: _items.length,
    shrinkWrap: true,
    physics: NeverScrollableScrollPhysics(),
    padding: EdgeInsets.all(10),
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 5,
      mainAxisSpacing: 10,
      crossAxisSpacing: 10,
    ),
    itemBuilder: (context, index) {
      return Draggable<String>(
        onDragCompleted: (){
          // 在拖動結束後刪除數據
          setState(() {
            _items.removeAt(index);
          });
        },
        feedback: Material(
          child: Container(
            height: 100,
            width: 100,
            color: Colors.blueAccent,
            alignment: Alignment.center,
            child: Text(
              _items[index],
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
        // 當前組件的數據
        data: _items[index],
        child: Container(
          color: Colors.blueAccent,
          alignment: Alignment.center,
          child: Text(
            _items[index],
            style: TextStyle(color: Colors.white),
          ),
        ),
      );
    },
  );
}
複製代碼

運行看一下效果:

能夠看到這樣就基本完成咱們的需求了,可是有人說,能不能把我拖着的源控件加個特效?

沒問題,咱們經過 childWhenDragging參數來控制。

如,加個蒙層:

childWhenDragging: Container(
  color: Colors.blueAccent,
  alignment: Alignment.center,
  foregroundDecoration: BoxDecoration(color: Colors.white30),
  child: Text(
    _items[index],
    style: TextStyle(color: Colors.white),
  ),
),
複製代碼

添加一個foregroundDecoration 就ok了,效果以下:

總結

經過這個小例子咱們能夠實現特別多的效果。

並且默認拖動的控件時能夠多指觸控的,也就是說咱們能夠同時拖動N個控件。

能夠經過 Draggable 的 maxSimultaneousDrags 來控制。

構造函數裏其餘的參數你們能夠自行了解一下。

不得不說 Flutter 是真的好用。

關注我,天天更新 Flutter & Dart 知識。

完整代碼已經傳至GitHub:github.com/wanglu1209/…

相關文章
相關標籤/搜索