koraxis
koraxis

Reputation: 811

Flutter: Drag and drop with Grid

I want to create a widget where you can add multiple widgets with different sizes and can change their position by using the drag and drop technique. Something like a grid view with drag and drop where you can change the position both horizontally and vertically. While you are dragging the selected widget, other widgets will move around to open up space for it.

Does anyone have any suggestion where to start or are there already some examples that are implementing what I am looking for?

Upvotes: 17

Views: 24154

Answers (6)

CopsOnRoad
CopsOnRoad

Reputation: 267554

enter image description here


You can also make use of LongPressDraggable, for this you need to long press your widget and then only you can drag it.

Offset _offset = Offset.zero;

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(),
    body: LayoutBuilder(
      builder: (context, constraints) {
        return Stack(
          children: [
            Positioned(
              left: _offset.dx,
              top: _offset.dy,
              child: LongPressDraggable(
                feedback: FlutterLogo(colors: Colors.orange, size: 100),
                child: FlutterLogo(colors: Colors.green, size: 100),
                onDragEnd: (details) {
                  setState(() {
                    final adjustment = MediaQuery.of(context).size.height - constraints.maxHeight;
                    _offset = Offset(details.offset.dx, details.offset.dy - adjustment);
                  });
                },
              ),
            ),
          ],
        );
      },
    ),
  );
}

Upvotes: 2

Hansheng
Hansheng

Reputation: 129

I've created a package called reorderables that solved this problem. You just need to tell the package your function to be called when drag and drop is done onReorder(int oldIndex, int newIndex).

This example has 9 icon widgets in a grid - Screenshot: ReorderableWrap

class _WrapExampleState extends State<WrapExample> {
  final double _iconSize = 90;
  List<Widget> _tiles;

  @override
  void initState() {
    super.initState();
    _tiles = <Widget>[
      Icon(Icons.filter_1, key: ValueKey(1), size: _iconSize),
      Icon(Icons.filter_2, key: ValueKey(2), size: _iconSize),
      Icon(Icons.filter_3, key: ValueKey(3), size: _iconSize),
      Icon(Icons.filter_4, key: ValueKey(4), size: _iconSize),
      Icon(Icons.filter_5, key: ValueKey(5), size: _iconSize),
      Icon(Icons.filter_6, key: ValueKey(6), size: _iconSize),
      Icon(Icons.filter_7, key: ValueKey(7), size: _iconSize),
      Icon(Icons.filter_8, key: ValueKey(8), size: _iconSize),
      Icon(Icons.filter_9, key: ValueKey(9), size: _iconSize),
    ];
  }

  @override
  Widget build(BuildContext context) {
    void _onReorder(int oldIndex, int newIndex) {
      setState(() {
        Widget row = _tiles.removeAt(oldIndex);
        _tiles.insert(newIndex, row);
      });
    }

    return ReorderableWrap(
      spacing: 8.0,
      runSpacing: 4.0,
      padding: const EdgeInsets.all(8),
      children: _tiles,
      onReorder: _onReorder
    );
  }
}

If you want to limit the number of columns, you can use an optional parameter named maxMainAxisCount

Upvotes: 8

CopsOnRoad
CopsOnRoad

Reputation: 267554

Although this may not answer your question but people who are looking for simple drag and drop widget, then here is the example.

See my 2nd answer for more simpler way

enter image description here

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Drag app"),
        ),
        body: HomePage(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage> {
  double width = 100.0, height = 100.0;
  Offset position ;

  @override
  void initState() {
    super.initState();
    position = Offset(0.0, height - 20);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          left: position.dx,
          top: position.dy - height + 20,
          child: Draggable(
            child: Container(
              width: width,
              height: height,
              color: Colors.blue,
              child: Center(child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
            ),
            feedback: Container(
              child: Center(
                child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
              color: Colors.blue[300],
              width: width,
              height: height,
            ),
            onDraggableCanceled: (Velocity velocity, Offset offset){
              setState(() => position = offset);
            },
          ),
        ),
      ],
    );
  }
}

Upvotes: 15

CopsOnRoad
CopsOnRoad

Reputation: 267554

You can also try this easier one (It doesn't include Feedback)

enter image description here

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(body: HomePage()));
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Offset offset = Offset.zero;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          left: offset.dx,
          top: offset.dy,
          child: GestureDetector(
            onPanUpdate: (details) {
              setState(() {
                offset = Offset(offset.dx + details.delta.dx, offset.dy + details.delta.dy);
              });
            },
            child: Container(width: 100, height: 100, color: Colors.blue),
          ),
        ),
      ],
    );
  }
}

Upvotes: 19

Don Joe
Don Joe

Reputation: 21

I cannot write comments becaus of my reputation but I wanted to answer to this question from the comments of CopsOnRoad's answer:

I don't want show feedback view instead of that I want to drag original view. Is it possible?

If someones looking for this too, you could use: childWhenDragging: Container(). You're still dragging the feedback but the original child will be hidden.

        ...
        child: Draggable(
                child: Container(
                  width: width,
                  height: height,
                  color: Colors.blue,
                  child: Center(child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
                ),
                feedback: Container(
                  child: Center(
                    child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
                  color: Colors.blue[300],
                  width: width,
                  height: height,
                ),
                childWhenDragging: Container(), // <-- so it looks like the original view is beeing dragged
                onDraggableCanceled: (Velocity velocity, Offset offset){
                  setState(() => position = offset);
                },
              ),
       ...

Upvotes: 2

Tree
Tree

Reputation: 31371

Here is example of draggable text

class DraggableText extends StatefulWidget {
  final Offset initialOffset;
  final String text;

  DraggableText(this.text, this.initialOffset);

  @override
  _DraggableTextState createState() => new _DraggableTextState();
}

class _DraggableTextState extends State<DraggableText> {
  Offset position = new Offset(0.0, 0.0);

  @override
  void initState() {
    super.initState();
    position = widget.initialOffset;
  }

  @override
  Widget build(BuildContext context) {
    final item = new LabelBox(size: new Size.square(100.0), label: widget.text);
    final avatar = new LabelBox(
      size: new Size.square(150.0), label: widget.text, opacity: 0.4);
    final draggable = new Draggable(
      data: widget.text,
      feedback: avatar,
      child: item,
      childWhenDragging: new Opacity(opacity: 0.0, child: item),
      onDraggableCanceled: (velocity, offset) {
        print('_DragBoxState.build -> offset ${offset}');
        setState(() => position = offset);
      });
    return new Positioned(
      left: position.dx, top: position.dy, child: draggable);
  }
}

You can check full example and a more advanced one here https://github.com/rxlabz/flutter_dropcity

Upvotes: 2

Related Questions