【问题标题】:Flutter Drag and drop listitem between multiple listsFlutter 在多个列表之间拖放列表项
【发布时间】:2021-01-08 12:28:10
【问题描述】:

我想开发,例如将一个列表项拖到另一个可能为空的列表中。如果 dragitem 放置在另一个列表的特定项目上,则拖动的项目将被添加到它拖动到的列表的索引中,并且同样的事情也将适用于同一个列表。我试过了,但在选择拖动项时我无法让它像滚动列表视图一样,并且只有 3 个可见项目。

我想这样开发:https://i.stack.imgur.com/bdn1I.gif

任何帮助表示赞赏。

【问题讨论】:

    标签: flutter flutter-layout


    【解决方案1】:

    【讨论】:

      【解决方案2】:

      您尝试开发的内容可以使用 DraggableDragTarget 来完成,但您必须注意 Draggable 和 DragTarget 都应该具有相同的数据类型

      我将简要解释这两个小部件,然后在本段之后我将发布完整代码,然后我将分别解释每个部分。

      Draggable 是用于使用户能够将一个小部件从一个位置拖动到另一个位置的小部件,但它只能在相同类型的 DragTarget 小部件上拖放和处理

      import 'package:flutter/material.dart';
      import 'package:flutter/scheduler.dart';
      import 'package:flutter/services.dart';
      
      void main() => runApp(MyApp());
      
      class MyApp extends StatelessWidget {
        const MyApp({Key key}) : super(key: key);
      
        @override
        Widget build(BuildContext context) =>
            MaterialApp(
              home: Drag(),
            );
      }
      
      class Drag extends StatefulWidget {
        @override
        _DragState createState() => _DragState();
      }
      
      class _DragState extends State<Drag> {
        List listA = ["A", "B", "C"];
        List listB = ["D", "E", "F"];
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            backgroundColor: Colors.greenAccent[200],
            body: SafeArea(
              child: Column(
                children: [
      //            list view separated will build a widget between 2 list items to act as a separator
                  Expanded(
                      child: ListView.separated(
                        itemBuilder: _buildListAItems,
                        separatorBuilder: _buildDragTargetsA,
                        itemCount: listA.length,
                      )),
                  Expanded(
                      child: ListView.separated(
                        itemBuilder: _buildListBItems,
                        separatorBuilder: _buildDragTargetsB,
                        itemCount: listB.length,
                      )),
                ],
              ),
            ),
          );
        }
      
      //  builds the widgets for List B items
        Widget _buildListBItems(BuildContext context, int index) {
          return Draggable<String>(
      //      the value of this draggable is set using data
            data: listB[index],
      //      the widget to show under the users finger being dragged
            feedback: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  listB[index],
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
      //      what to display in the child's position when being dragged
            childWhenDragging: Container(
              color: Colors.grey,
              width: 40,
              height: 40,
            ),
      //      widget in idle state
            child: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  listB[index],
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          );
        }
      
      //  builds the widgets for List A items
        Widget _buildListAItems(BuildContext context, int index) {
          return Draggable<String>(
            data: listA[index],
            feedback: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  listA[index],
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
            childWhenDragging: Container(
              color: Colors.grey,
              width: 40,
              height: 40,
            ),
            child: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  listA[index],
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          );
        }
      
      //  will return a widget used as an indicator for the drop position
        Widget _buildDropPreview(BuildContext context, String value) {
          return Card(
            color: Colors.lightBlue[200],
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(
                value,
                style: TextStyle(fontSize: 20),
              ),
            ),
          );
        }
      
      //  builds DragTargets used as separators between list items/widgets for list A
        Widget _buildDragTargetsA(BuildContext context, int index) {
          return DragTarget<String>(
      //      builder responsible to build a widget based on whether there is an item being dropped or not
            builder: (context, candidates, rejects) {
              return candidates.length > 0 ? _buildDropPreview(context, candidates[0]):
                  Container(
                    width: 5,
                    height: 5,
                  );
            },
      //      condition on to accept the item or not
            onWillAccept: (value)=>!listA.contains(value),
      //      what to do when an item is accepted
            onAccept: (value) {
              setState(() {
                listA.insert(index + 1, value);
                listB.remove(value);
              });
            },
          );
        }
      
      //  builds drag targets for list B
        Widget _buildDragTargetsB(BuildContext context, int index) {
          return DragTarget<String>(
            builder: (context, candidates, rejects) {
              return candidates.length > 0 ? _buildDropPreview(context, candidates[0]):
              Container(
                width: 5,
                height: 5,
              );
            },
            onWillAccept: (value)=>!listB.contains(value),
            onAccept: (value) {
              setState(() {
                listB.insert(index + 1, value);
                listA.remove(value);
              });
            },
          );
        }
      }
      
      

      以下部分负责为ListB构建Draggable Widget,这个Draggable Widget会根据索引显示列表的值,并将列表项的值设置为可拖动的数据,以便dragTarget可以处理可拖动。

      //  builds the widgets for List B items
        Widget _buildListBItems(BuildContext context, int index) {
          return Draggable<String>(
      //      the value of this draggable is set using data
            data: listB[index],
      //      the widget to show under the users finger being dragged
            feedback: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  listB[index],
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
      //      what to display in the child's position when being dragged
            childWhenDragging: Container(
              color: Colors.grey,
              width: 40,
              height: 40,
            ),
      //      widget in idle state
            child: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  listB[index],
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ),
          );
        }
      

      这部分代码将处理来自listB的可拖动对象(如果您修改代码,也可以是listA),它将根据它们的值接受/拒绝可拖动对象,如果可拖动对象可以,它将显示一个小部件被接受,它会使用修改后的列表设置状态,以便创建新状态并更改用户界面。

      //  builds DragTargets used as separators between list items/widgets for list A
        Widget _buildDragTargetsA(BuildContext context, int index) {
          return DragTarget<String>(
      //      builder responsible to build a widget based on whether there is an item being dropped or not
            builder: (context, candidates, rejects) {
              return candidates.length > 0 ? _buildDropPreview(context, candidates[0]):
                  Container(
                    width: 5,
                    height: 5,
                  );
            },
      //      condition on to accept the item or not
            onWillAccept: (value)=>!listA.contains(value),
      //      what to do when an item is accepted
            onAccept: (value) {
              setState(() {
                listA.insert(index + 1, value);
                listB.remove(value);
              });
            },
          );
        }
      

      最后在这段代码中,正如我上面提到的,它只接受从列表 A 到列表 B 的可拖动对象,反之亦然,但它不接受从同一个列表到同一个列表的可拖动对象。除此之外,还有一些我没有考虑但你可以考虑的情况,这些情况是:

      1. 将项目置于列表顶部
      2. 将项目放在列表底部
      3. 将项目从列表移动到空列表

      example 1

      example 2

      希望我的回答对你有帮助!

      【讨论】:

      • 感谢您的帮助。但是如果我们将 dragabbleitem 拖到另一个 listitem 并且它的索引将被 draggableitem 占用呢?
      • 索引用于从列表中获取一个项目,并将该项目分配给可拖动项目“数据”,因此每个可拖动项目都有一个数据值。
      • 是的,没关系,但是如果我将项目悬停在而不是 dragarea 上意味着例如列表 2 的 B 悬停在列表 1 的 A 上然后?
      • 你可以看到我附有问题的 .gif。在那里你可以看到 d 被拖到 a 并且它占据了 a 的位置。当拖动项目悬停时,您是否做过类似该滚动列表的另一个问题?
      • 在我的代码中,项目占据它们之间的位置,所以如果你想占据 B 的位置,你必须把项目放在 A 和 B 之间。如果你想占据 A 的位置,你必须把它在 A 之上(这是我在代码中没有做的情况),我将编辑答案以发布一个 gif 展示它是如何工作的
      猜你喜欢
      • 2019-07-04
      • 2020-06-04
      • 2017-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多