How to create a movable widget in flutter such that is stays at the position it is dragged to
Asked Answered
M

2

10

How can i create a movable/draggable widget in flutter that stays at the position it is dragged to .I tried using draggable widget but the widget which gets wrapped in draggable returns back to the original position after releasing the the drag.

As you can see in this GIF that the draggable object returns back to its original position. How to make it stay there

Malleolus answered 31/1, 2021 at 9:47 Comment(0)
K
17

On top of dragging the object around, you can also make it zoomable with the help of a GestureDetector. I applied the GestureDetector to the main Stack so that you can pinch to zoom in/out anywhere on the screen. It makes it somewhat easier to see what you are doing.

enter image description here


HookWidget version

class DragArea extends HookWidget {
  final Widget child;

  const DragArea({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final position = useState(Offset(100, 100));
    final prevScale = useState(1.0);
    final scale = useState(1.0);
    return GestureDetector(
      onScaleUpdate: (details) => scale.value = prevScale.value * details.scale,
      onScaleEnd: (_) => prevScale.value = scale.value,
      child: Stack(
        children: [
          Positioned.fill(
              child: Container(color: Colors.amber.withOpacity(.4))),
          Positioned(
            left: position.value.dx,
            top: position.value.dy,
            child: Draggable(
              maxSimultaneousDrags: 1,
              feedback: Transform.scale(
                scale: scale.value,
                child: child,
              ),
              childWhenDragging: Opacity(
                opacity: .3,
                child: Transform.scale(
                  scale: scale.value,
                  child: child,
                ),
              ),
              onDragEnd: (details) => position.value = details.offset,
              child: Transform.scale(
                scale: scale.value,
                child: child,
              ),
            ),
          )
        ],
      ),
    );
  }
}

StatefulWidget version

class StatefulDragArea extends StatefulWidget {
  final Widget child;

  const StatefulDragArea({Key key, this.child}) : super(key: key);

  @override
  _DragAreaStateStateful createState() => _DragAreaStateStateful();
}

class _DragAreaStateStateful extends State<StatefulDragArea> {
  Offset position = Offset(100, 100);
  double prevScale = 1;
  double scale = 1;

  void updateScale(double zoom) => setState(() => scale = prevScale * zoom);
  void commitScale() => setState(() => prevScale = scale);
  void updatePosition(Offset newPosition) =>
      setState(() => position = newPosition);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onScaleUpdate: (details) => updateScale(details.scale),
      onScaleEnd: (_) => commitScale(),
      child: Stack(
        children: [
          Positioned.fill(
              child: Container(color: Colors.amber.withOpacity(.4))),
          Positioned(
            left: position.dx,
            top: position.dy,
            child: Draggable(
              maxSimultaneousDrags: 1,
              feedback: widget.child,
              childWhenDragging: Opacity(
                opacity: .3,
                child: widget.child,
              ),
              onDragEnd: (details) => updatePosition(details.offset),
              child: Transform.scale(
                scale: scale,
                child: widget.child,
              ),
            ),
          ),
        ],
      ),
    );
  }
}
Klepht answered 4/2, 2021 at 21:56 Comment(3)
Thanks again and the horse is just really funnyMalleolus
Hahaha! This is what I looked for since I didn't have your image: "funny horse", line drawing.Klepht
What is the best way to save this & then re-display it, where it is balanced on all screen sizes? Basically, if this were a Story-style feature like you see on Instagram or Snapchat?Divaricate
K
2

You need to manage the position of the dragged item(s) with some kind of State Management. In the following code sample, I use Flutter Hooks useState.

enter image description here

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

const imgData =
    'data:image/jpeg;base64,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';

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        body: DragArea(
          child: Image.network(imgData),
        ),
      ),
    ),
  );
}

class DragArea extends HookWidget {
  final Widget child;

  const DragArea({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final position = useState(Offset(100, 100));
    return Stack(
      children: [
        Positioned(
          left: position.value.dx,
          top: position.value.dy,
          child: Draggable(
            feedback: child,
            childWhenDragging: Opacity(
              opacity: .3,
              child: child,
            ),
            onDragEnd: (details) => position.value = details.offset,
            child: child,
          ),
        )
      ],
    );
  }
}

Stateful version

as requested by anikait

class StatefulDragArea extends StatefulWidget {
  final Widget child;

  const StatefulDragArea({Key key, this.child}) : super(key: key);

  @override
  _DragAreaStateStateful createState() => _DragAreaStateStateful();
}

class _DragAreaStateStateful extends State<StatefulDragArea> {
  Offset position = Offset(100, 100);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned(
          left: position.dx,
          top: position.dy,
          child: Draggable(
            feedback: widget.child,
            childWhenDragging: Opacity(
              opacity: .3,
              child: widget.child,
            ),
            onDragEnd: (details) => setState(() => position = details.offset),
            child: widget.child,
          ),
        )
      ],
    );
  }
}
Klepht answered 31/1, 2021 at 11:27 Comment(5)
Is there a simpler way to do this as i dont know how to use flutter hooks.Malleolus
Simpler, no. 😋 Flutter Hooks are simplifying StatefulWidget. I will update my answer with a StatefulWidget version. But have a look at both and join us on the Flutter Hooks / Riverpod side of the Force.Klepht
thanks for help, but can u tell how to make the widget zoomable ,so that we can zoom in and out as well as drag the widget to any part of the screen kind of like sticker over images functionalityMalleolus
Sure, I created a second answer to keep the first answer concise and focused on the draggable feature.Klepht
@Klepht Widget is going outside of screen can you make is stay inside, mean if we drag it outside of screen it stays there but in real time It should get back into visible screen or in other words we can say it should stay inside screen pixel limits, can you help us in this regard?Wintertide

© 2022 - 2024 — McMap. All rights reserved.