skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    Based on Kumah Andrews' answer I fixed the reorder in the right column. Stillstuggling with combining reordering in the left. Using the library caused it to only accept one drag-action at a time.

    Here's the updated code:

    import 'package:flutter/material.dart';
    
    class DragDropReorderableExample extends StatefulWidget {
      const DragDropReorderableExample({super.key});
    
      @override
      DragDropReorderableExampleState createState() =>
          DragDropReorderableExampleState();
    }
    
    class DragDropReorderableExampleState
        extends State<DragDropReorderableExample> {
      List<String> libraryCards = [
        "Card A",
        "Card B",
        "Card C",
        "Card D",
        "Card E",
        "Card F",
      ];
      List<String> droppedCards = [];
    
      int? dropIndex;
      int dragIndex = -1;
      bool isDraggingInsideRight = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Drag and Drop Example')),
          body: Row(
            children: [
              Expanded(
                flex: 3,
                child: _getLibraryWidget(),
              ),
              Expanded(
                flex: 1,
                child: _getDroppableContainer(),
              ),
            ],
          ),
        );
      }
    
      Widget _getLibraryWidget() {
        return Container(
          height: double.infinity,
          color: Colors.blue[50],
          padding: const EdgeInsets.all(8),
          child: Wrap(
            spacing: 8,
            runSpacing: 8,
            children: libraryCards.map((card) {
              return Draggable<String>(
                data: card,
                feedback: Card(
                  elevation: 10,
                  color: Colors.blue[200],
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Text(card),
                  ),
                ),
                childWhenDragging: Opacity(
                  opacity: 0.5,
                  child: Card(
                    color: Colors.blue[100],
                    child: Padding(
                      padding: const EdgeInsets.all(16),
                      child: Text(card),
                    ),
                  ),
                ),
                onDragStarted: () =>
                    setState(() => isDraggingInsideRight = false),
                child: Card(
                  elevation: 10,
                  color: Colors.blue[100],
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Text(card),
                  ),
                ),
              );
            }).toList(),
          ),
        );
      }
    
      Widget _getDroppableContainer() {
        return Container(
          color: Colors.green[50],
          padding: const EdgeInsets.all(8),
          child: DragTarget<String>(
            onAcceptWithDetails: (data) {
              setState(() {
                droppedCards.insert(
                  dropIndex ?? droppedCards.length,
                  data.data,
                );
    
                if (isDraggingInsideRight) {
                  if (dragIndex < dropIndex!) {
                    droppedCards.removeAt(dragIndex);
                  } else {
                    droppedCards.removeAt(dragIndex + 1);
                  }
                }
    
                dropIndex = null;
                dragIndex = -1;
              });
            },
            onMove: (details) {
              RenderBox renderBox = context.findRenderObject() as RenderBox;
              double localDy = renderBox.globalToLocal(details.offset).dy;
              double itemHeight = 70; // Approximate height of each card
              dropIndex =
                  (localDy / itemHeight).floor().clamp(0, droppedCards.length);
              setState(() {});
            },
            onLeave: (_) => setState(() => dropIndex = null),
            builder: (context, candidateData, rejectedData) {
              return Column(
                children: [
                  for (int i = 0; i <= droppedCards.length; i++) ...[
                    if (i == dropIndex)
                      // Replace with SizedBox to show a gap between cards
                      // const SizedBox(height: 40),
                    const Divider(
                      color: Colors.green,
                      thickness: 3,
                    ),
                    if (i < droppedCards.length)
                      Draggable<String>(
                        data: droppedCards[i],
                        feedback: Card(
                          color: Colors.green[200],
                          child: Container(
                            decoration: BoxDecoration(
                              border: Border.all(
                                color: Colors.green,
                                width: 2,
                              ),
                              borderRadius: BorderRadius.circular(20),
                            ),
                            child: Padding(
                              padding: const EdgeInsets.all(16),
                              child: Text(droppedCards[i]),
                            ),
                          ),
                        ),
                        childWhenDragging: const SizedBox(),
                        onDragStarted: () {
                          setState(() {
                            isDraggingInsideRight = true;
                            dragIndex = i;
                          });
                        },
                        child: dragIndex == i
                            ? const SizedBox()
                            : Card(
                                color: Colors.green[100],
                                child: Padding(
                                  padding: const EdgeInsets.all(16),
                                  child: Text(droppedCards[i]),
                                ),
                              ),
                      ),
                  ],
                ],
              );
            },
          ),
        );
      }
    }
    

  2.     import 'package:flutter/material.dart';
    
    
    class DragDropExample extends StatefulWidget {
      @override
      _DragDropExampleState createState() => _DragDropExampleState();
    }
    
    class _DragDropExampleState extends State<DragDropExample> {
      List<String> libraryCards = ["Card A", "Card B", "Card C"];
      List<String> droppedCards = [];
    
      int? dropIndex; // To track where the card will be dropped
    
      @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: Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: libraryCards.map((card) {
                      return Draggable<String>(
                        data: card,
                        feedback: Material(
                          child: Card(
                            color: Colors.blue[200],
                            child: Padding(
                              padding: const EdgeInsets.all(16),
                              child: Text(card),
                            ),
                          ),
                        ),
                        childWhenDragging: Opacity(
                          opacity: 0.5,
                          child: Card(
                            color: Colors.blue[100],
                            child: Padding(
                              padding: const EdgeInsets.all(16),
                              child: Text(card),
                            ),
                          ),
                        ),
                        child: Card(
                          color: Colors.blue[100],
                          child: Padding(
                            padding: const EdgeInsets.all(16),
                            child: Text(card),
                          ),
                        ),
                      );
                    }).toList(),
                  ),
                ),
              ),
    
              // Right Container: Droppable List
              Expanded(
                flex: 2,
                child: Container(
                  color: Colors.green[50],
                  padding: const EdgeInsets.all(8),
                  child: DragTarget<String>(
                    onAccept: (data) {
                      setState(() {
                        droppedCards.insert(dropIndex ?? droppedCards.length, data);
                        dropIndex = null; // Reset the drop index
                      });
                    },
                    onMove: (details) {
                      RenderBox renderBox = context.findRenderObject() as RenderBox;
                      double localDy = renderBox.globalToLocal(details.offset).dy;
                      double itemHeight = 70; // Approximate height of each card
                      dropIndex = (localDy / itemHeight).floor().clamp(0, droppedCards.length);
                      setState(() {});
                    },
                    onLeave: (_) => setState(() => dropIndex = null),
                    builder: (context, candidateData, rejectedData) {
                      return Column(
                        children: [
                          for (int i = 0; i <= droppedCards.length; i++) ...[
                            if (i == dropIndex)
                              const Divider(
                                color: Colors.green,
                                thickness: 3,
                              ),
                            if (i < droppedCards.length)
                              Draggable<String>(
                                data: droppedCards[i],
                                feedback: Material(
                                  child: Card(
                                    color: Colors.green[200],
                                    child: Padding(
                                      padding: const EdgeInsets.all(16),
                                      child: Text(droppedCards[i]),
                                    ),
                                  ),
                                ),
                                childWhenDragging: Opacity(
                                  opacity: 0.5,
                                  child: Card(
                                    color: Colors.green[100],
                                    child: Padding(
                                      padding: const EdgeInsets.all(16),
                                      child: Text(droppedCards[i]),
                                    ),
                                  ),
                                ),
                                onDragCompleted: () => droppedCards.removeAt(i),
                                child: Card(
                                  color: Colors.green[100],
                                  child: Padding(
                                    padding: const EdgeInsets.all(16),
                                    child: Text(droppedCards[i]),
                                  ),
                                ),
                              ),
                          ],
                        ],
                      );
                    },
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search