make Draggable not return when its canceled - flutter

I am trying to create a draggable container which can be dragged around the screen. The problem i faced that Draggable always returns to it's prevouis postition.https://imgur.com/a/0ESoCZW
So how can you create Draggable that stays at the position where you left of your finger.

You can use onDraggableCanceled property to change the location.
i hope that following example will clear your idea.
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
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(
"currently dragging",
style: Theme.of(context).textTheme.headline,
),
),
color: Colors.blue[300],
width: width,
height: height,
),
onDraggableCanceled: (Velocity velocity, Offset offset) {
setState(() => position = offset);
},
),
),
],
);
}
}

Related

Flutter change offset of draggable widgets in a stack at the same time as scrolling a SingleChildScrollView

I have a stack of widgets in Flutter. The lowest stack is a large image that is contained inside SingleChildScrollView. I've set up a ScrollController so I know when the image has been horizontally scrolled in the view.
Next in the stack I have several Positioned widgets (these are draggable so that they can moved around independent of each other).
What I'd like to do is when the SingleChildScrollView is scrolled, I'd like to update the position of each of the positioned widgets higher up the stack.
I've considered Stream, rebuildAllChildren and ValueNotifier but all seem quite complex for what, on the face of it, should be quite a simple thing to achieve. I'm probably missing something very obvious somewhere!
Here's my code so far:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LayoutScreen extends StatefulWidget {
LayoutScreen();
#override
_LayoutScreenState createState() => _LayoutScreenState();
}
class _LayoutScreenState extends State<LayoutScreen> {
ScrollController _controller;
Offset boxoneposition;
BuildContext context;
_scrollListener() {
print(_controller.offset);
boxoneposition=Offset(100.0, 100);
setState(() {
print(boxoneposition);
// this was a test - the value of boxoneposition is updated, however the view isn't
});
}
#override
void initState() {
_controller = ScrollController();
_controller.addListener(_scrollListener);
boxoneposition = Offset(0.0, 30);
super.initState();
}
DragBox boxOne() {
// Trying to set up outside of widget build
return DragBox(boxoneposition, 'Box One Yep', Colors.blueAccent, 1);
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _controller,
child: Container(
width: 1000,
height: 1000,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/logo.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
//DragBox(boxoneposition, 'Box One', Colors.blueAccent, 1),
boxOne(),
DragBox(Offset(200.0, 50.0), 'Box Two', Colors.orange, 2),
DragBox(Offset(300.0, 80.0), 'Box Three', Colors.lightGreen, 3),
],
);
}
}
class DragBox extends StatefulWidget {
final Offset initPos;
final String label;
final Color itemColor;
final int boxnumber;
DragBox(this.initPos, this.label, this.itemColor, this.boxnumber);
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position = Offset(0.0, 0.0);
String imagePath="";
#override
void initState() {
super.initState();
position = widget.initPos;
}
getBoxPic() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
String key='picture'+widget.boxnumber.toString();
imagePath=prefs.getString(key);
});
print(imagePath);
return File(imagePath);
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
data: widget.itemColor,
child: Container(
width: 100.0,
height: 100.0,
color: widget.itemColor,
child: Center(
child:
Image.asset('assets/images/logo.png')
),
),
onDragStarted: () {
setState(() {
print("Foobar");
});
},
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
if (widget.boxnumber==1) {
print("Wibble");
}
});
},
feedback: Container(
width: 120.0,
height: 120.0,
color: widget.itemColor.withOpacity(0.5),
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 18.0,
),
),
),
),
)
);
}
}
Any help greatly appreciated!
I was being a fool. Funny how walking away from something overnight and coming back to it clears the fog!
I sorted this out by simply placing an entire stack into the SingleChildScrollView. The scrollview is wider and horizontally scrollable and the rest of the elements in the stack correctly maintain their positons even if the scrollview is moved and they disappear off screen.
The solution was very simple. For completeness, here's the updated code:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LayoutScreen extends StatefulWidget {
LayoutScreen();
#override
_LayoutScreenState createState() => _LayoutScreenState();
}
class _LayoutScreenState extends State<LayoutScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Arrange your images'),
backgroundColor: Colors.orange,
),
body: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Stack(
children: <Widget>[
Container(
width: 1000,
height: 1000,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/background.jpg'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
DragBox(Offset(100.0, 10.0), 'Box One', Colors.blueAccent, 1),
DragBox(Offset(200.0, 50.0), 'Box Two', Colors.orange, 2),
DragBox(Offset(300.0, 80.0), 'Box Three', Colors.lightGreen, 3),
]
),
)
);
}
}
class DragBox extends StatefulWidget {
final Offset initPos;
final String label;
final Color itemColor;
final int boxnumber;
DragBox(this.initPos, this.label, this.itemColor, this.boxnumber);
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position = Offset(0.0, 0.0);
String imagePath="";
#override
void initState() {
super.initState();
position = widget.initPos;
}
getBoxPic() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
String key='picture'+widget.boxnumber.toString();
imagePath=prefs.getString(key);
});
print(imagePath);
return File(imagePath);
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
data: widget.itemColor,
child: Container(
width: 100.0,
height: 100.0,
color: widget.itemColor,
child: Center(
child:
Image.asset('assets/images/logo.png')
),
),
onDragStarted: () {
setState(() {
print("Foobar");
});
},
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
if (widget.boxnumber==1) {
print("Wibble");
}
});
},
feedback: Container(
width: 120.0,
height: 120.0,
color: widget.itemColor.withOpacity(0.5),
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 18.0,
),
),
),
),
)
);
}
}

Drag and drop offset error when draggable is inside ListView

I was making a new app and I have the need to put one or more drag and drop zones inside a ListView with draggable elements, there is no need of a drop target.
When the draggable element is dropped there is a big offset error on the vertical position.
I tried to "correct" the offset position by hand subtracting 100px to the vertical position, this is definetely not ideal.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Drag and drop bug"),
),
body: App(),
),
);
}
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
Color caughtColor = Colors.grey;
Container spacer({double height: 400, Color color: Colors.blue}) {
return Container(
color: color,
height: height,
);
}
#override
Widget build(BuildContext context) {
// I need a ListView because I have other elements before and after the stack that will certainly occupy more than the view height, the spacers simulate these elements
return ListView(
children: <Widget>[
spacer(color: Colors.amber),
Container(
height: 400,
child: Stack(
children: <Widget>[
DragBox(Offset(0.0, 0.0), 'Box One', Colors.blueAccent),
DragBox(Offset(200.0, 0.0), 'Box Two', Colors.orange),
DragBox(Offset(300.0, 0.0), 'Box Three', Colors.lightGreen),
],
),
),
spacer(color: Colors.cyan)
],
);
}
}
class DragBox extends StatefulWidget {
final Offset initPos;
final String label;
final Color itemColor;
DragBox(this.initPos, this.label, this.itemColor);
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position = Offset(0.0, 0.0);
#override
void initState() {
super.initState();
position = widget.initPos;
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
data: widget.itemColor,
child: Container(
width: 100.0,
height: 100.0,
color: widget.itemColor,
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 20.0,
),
),
),
),
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
});
},
feedback: Container(
width: 120.0,
height: 120.0,
color: widget.itemColor.withOpacity(0.5),
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 18.0,
),
),
),
),
),
);
}
}
The expected result is to have the dropped element under the cursor/pointer but it is moved of about 100px on the vertical axis.
The offset difference is due to the height of the AppBar.
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
});
}
Here the offset is given in the global coordinate system where the origin is at the top and extreme left of the screen.
The solution to this is given here: How to move element anywhere inside parent container with drag and drop in Flutter?

How to make scrollable Drag and Drop in Flutter?

I am trying to make a scrollable and zoomable Stack using SingleChildScrollView in order to implement a canvas editor.
The Drag and Drop works perfectly when I put my dragabble item in the initial view but when I scroll down the view and I tried to drop my container is coming back in the initial view.
I'm new to the Flutter development so maybe I missunderstood in the implementation of a such thing.
Here's the code I currently have.
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: App(),
),
);
}
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
Color caughtColor = Colors.grey;
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Stack(
children: <Widget>[
Container(
height: 2000,
),
DragBox(Offset(0.0, 0.0), 'Box One', Colors.blueAccent),
],
),
);
}
}
class DragBox extends StatefulWidget {
final Offset initPos;
final String label;
final Color itemColor;
DragBox(this.initPos, this.label, this.itemColor);
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position = Offset(0.0, 0.0);
#override
void initState() {
super.initState();
position = widget.initPos;
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
data: widget.itemColor,
child: Container(
width: 100.0,
height: 100.0,
color: widget.itemColor,
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 20.0,
),
),
),
),
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
});
},
feedback: Container(
width: 120.0,
height: 120.0,
color: widget.itemColor.withOpacity(0.5),
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 18.0,
),
),
),
),
));
}
}
Any suggestions or code samples would be really helpful to me.
I solve the problem like this, wrap your draggable with a listener.
Listener listenableDraggable = Listener(
child: draggable,
onPointerMove: (PointerMoveEvent event) {
if (event.position.dy > MediaQuery.of(context).size.height) {
// 120 is height of your draggable.
scrollController.scrollTo(scrollcontroller.offset + 120);
}
},
);

Flutter how to pass back data from model

I'm practicing draggable widget. Thanks to amazing video by Tensor Programming. I was able to understand how things work.
However, there is one thing I'm not sure. Let's say if one of the DragBox tapped, I want to get text inside of the tapped DragBox. I wrapped Container with GestureDetector, but I have no idea how to make onTap function pass widget.label to AppState.
Should I use constructor or something to pass it?
Does anyone know hot to achieve this?
Thanks in advance.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: App(),
),
);
}
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
Color caughtColor = Colors.grey;
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
DragBox(Offset(0.0, 0.0), 'Box One', Colors.blueAccent),
DragBox(Offset(200.0, 0.0), 'Box Two', Colors.orange),
DragBox(Offset(300.0, 0.0), 'Box Three', Colors.lightGreen),
Positioned(
left: 100.0,
bottom: 0.0,
child: DragTarget(
onAccept: (Color color) {
caughtColor = color;
},
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: accepted.isEmpty ? caughtColor : Colors.grey.shade200,
),
child: Center(
child: Text("Drag Here!"),
),
);
},
),
)
],
);
}
}
class DragBox extends StatefulWidget {
final Offset initPos;
final String label;
final Color itemColor;
DragBox(this.initPos, this.label, this.itemColor);
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position = Offset(0.0, 0.0);
#override
void initState() {
super.initState();
position = widget.initPos;
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
data: widget.itemColor,
child: GestureDetector(
onTap() {
// want to pass widget.label here
},
child: Container(
width: 100.0,
height: 100.0,
color: widget.itemColor,
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 20.0,
),
),
),
),
),
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
});
},
feedback: Container(
width: 120.0,
height: 120.0,
color: widget.itemColor.withOpacity(0.5),
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 18.0,
),
),
),
),
));
}
}
Can you please refer this answer. In the link
RootPage is similar to App,
AppState is similar to _RootPageState,
DragBox is similar to FeedPage and
DragBoxState is similar to _feedPageState
If it is not helpful, please comment, i can put example for your App

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?
You can also try this easier one (It doesn't include Feedback)
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),
),
),
],
);
}
}
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
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);
},
),
),
],
);
}
}
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
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
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);
},
),
...
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);
});
},
),
),
],
);
},
),
);
}