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.
How to create a movable widget in flutter such that is stays at the position it is dragged to
Asked Answered
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.
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,
),
),
),
],
),
);
}
}
Thanks again and the horse is just really funny –
Malleolus
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
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
.
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,
),
)
],
);
}
}
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 functionality –
Malleolus
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.