首页 > 解决方案 > Flutter 在多个列表之间拖放列表项

问题描述

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

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

任何帮助表示赞赏。

标签: flutterflutter-layout

解决方案


您尝试开发的内容可以使用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),它将根据它们的值接受/拒绝可拖动对象,如果可以接受可拖动对象,它将显示一个小部件,并且它将setState 与修改后的列表,以便创建新状态并更改 ui。

//  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. 将项目从列表移动到空列表

示例 1

示例 2

希望我的回答有帮助!


推荐阅读