flutterdrag-and-dropdraggable

Combining reorderable widgets (in a Wrap) with widgets in another container to achieve multiple drag-and-drop behaviors


I want to have two containers in a Row that each have their own lists of Card widgets.

  1. The first (left) container is a library of Cards which is a Wrap to make the cards flow horizontally. The Cards in this Wrap should be draggable/droppable to rearrange the order of the cards, and I was looking at this library to solve that.
  2. The second (right) container should be a Column containing another list of Card widgets. This container should also be draggable/droppable within itself, to rearrange the order of the cards.
  3. In addition to this, I want to be able to drag a Card from the library (left container) to the right container, adding the Card to the right containers list in the correct dropped position.
  4. The card should not be moved/removed from the left container, it should still stay there in the same place, but it should create a duplicate in the right container.
  5. There should be some kind of indicator as to where the Card will be added when dragging from left to right container (between other cards).

I think that combining the linked library with Flutter DragTarget could possibly be one working solution, but I'm not sure.


Solution

  • import 'package:flutter/material.dart';
    
    class DragDropExample extends StatefulWidget {
      @override
      _DragDropExampleState createState() => _DragDropExampleState();
    }
    
    class _DragDropExampleState extends State<DragDropExample> {
      List<Widget> libraryCards = [
        Card(
          color: Colors.blue[100],
          child: const Padding(
            padding: EdgeInsets.all(16),
            child: Text("Card A"),
          ),
        ),
        Card(
          color: Colors.blue[100],
          child: const Padding(
            padding: EdgeInsets.all(16),
            child: Text("Card B"),
          ),
        ),
        Card(
          color: Colors.blue[100],
          child: const Padding(
            padding: EdgeInsets.all(16),
            child: Text("Card C"),
          ),
        ),
      ];
      List<Widget> droppedCards = [];
    
      void _addNewItem(Widget child) {
        setState(() {
          droppedCards.insert(
            0,
            _buildListItem(child),
          );
        });
      }
    
      Widget _buildListItem(Widget child) {
        return SizedBox(key: UniqueKey(), child: child);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Drag and Drop Example')),
          body: Row(
            children: [
              // Left Container: Library
              Expanded(
                flex: 1,
                child: Container(
                  color: Colors.blue[50],
                  padding: const EdgeInsets.all(8),
                  child: ReorderableListView(
                    onReorder: (oldIndex, newIndex) {
                      setState(() {
                        if (newIndex > oldIndex) {
                          newIndex -= 1;
                        }
                        final item = libraryCards.removeAt(oldIndex);
                        libraryCards.insert(newIndex, item);
                      });
                    },
                    children: libraryCards
                        .map(
                          (card) => Draggable<Widget>(
                            key: UniqueKey(),
                              data: card,
                              feedback: Material(child: card),
                              childWhenDragging: Opacity(opacity: 0.5, child: card),
                              child: card),
                        )
                        .toList(),
                  ),
                ),
              ),
    
              // Right Container: Droppable List
              Expanded(
                flex: 2,
                child: Container(
                  color: Colors.green[50],
                  padding: const EdgeInsets.all(8),
                  child: DragTarget<Widget>(
                    onAccept: (data) {
                      setState(() {
                        _addNewItem(data);
                      });
                    },
                    builder: (context, candidateData, rejectedData) {
                      return ReorderableListView(
                        onReorder: (oldIndex, newIndex) {
                          setState(() {
                            if (newIndex > oldIndex) {
                              newIndex -= 1;
                            }
                            final item = droppedCards.removeAt(oldIndex);
                            droppedCards.insert(newIndex, item);
                          });
                        },
                        children: droppedCards,
                      );
                    },
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }
    

    enter image description here

    If you want to re-arrange the card library on the left or right, just long press and drag to the desired position. But to drag from left to right container just drag straight way. NB. I drop the item on the top of the list.