Flutter how to pass back data from model - flutter

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

Related

Flutter: Increase hitbox of GestureDetector

I am fairly new to flutter and currently trying to create a NavBar.
It looks like this:
If I click on the icon, the bar moves to the selected one and the content changes.
However, I have to hit the icon perfectly. I would like to have a "box" around it, so I can tap just near it. Basically divide the space into 3.
I tried the following:
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
The items should take 1/3 of the width. It isn't working that way tho. Any idea on how to increase the "tappable" space?
EDIT
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.\
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var screens = [Text("Button1"), Text("Button2"), Text("Button3")];
int currentScreen = 0;
void changeIndex(int index) => setState(() {
currentScreen = index;
});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.orange,
child: Stack(
children: [
SafeArea(child: screens[currentScreen]),
Container(
alignment: Alignment.bottomCenter, child: NavBar(changeIndex))
],
),
),
);
}
}
class MenuItem {
final String name;
final Color color;
final double offset;
MenuItem(this.name, this.color, this.offset);
}
class NavBar extends StatefulWidget {
#override
State<StatefulWidget> createState() => NavBarState(navBarUpdate);
late Function(int) navBarUpdate;
NavBar(this.navBarUpdate);
}
class NavBarState extends State<NavBar> {
var items = [
MenuItem("Test", Colors.red, -0.76),
MenuItem("Test2", Colors.green, 0),
MenuItem("Test3", Colors.yellow, 0.76)
];
late MenuItem active;
late Function(MenuItem) navBarUpdate;
#override
void initState() {
super.initState();
active = items[0];
}
NavBarState(Function(int) navBarUpdate) {
this.navBarUpdate = (item) {
navBarUpdate(items.indexOf(item));
};
}
#override
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
}
You can use behavior: HitTestBehavior.translucent, or opaque on createNavBarItem
child: GestureDetector(
behavior: HitTestBehavior.translucent,
You can swap your GestureDetector on top level widget from Icon.
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return GestureDetector(
child: Container(
color: Colors.transparent,
width: width / items.length,
height: 55,
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
);
}

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,
),
),
),
),
)
);
}
}

Access variable built in different file/class

I have file.a which contains an array:
final _likes = <String>[];
Based on answers selected by the user, items are added to this array.
I want to then be able to, at the press of a button, display the items in the array to the user.
The issue is that the button I want the users to press is defined in file.b (as the Icon is on the AppBar).
How can I give file.b the ability to see the variable _likes and access the data in it, when it lives in file.a?
file.a:
import 'package:flutter/material.dart';
import './main.dart';
class Images extends StatefulWidget {
#override
_ImagesState createState() => _ImagesState();
}
class _ImagesState extends State<Images> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
#override
// ignore: must_call_super
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
);
_animation = Tween(
begin: 0.0,
end: 1.0,
).animate(_controller);
}
#override
dispose() {
_controller.dispose();
super.dispose();
}
int index = 0;
final likes = <String>[];
#override
Widget build(BuildContext context) {
_controller.forward();
return GestureDetector(
onHorizontalDragEnd: (DragEndDetails dragEndDetails) {
if (dragEndDetails.primaryVelocity == 0) return;
if (dragEndDetails.primaryVelocity.compareTo(0) == -1)
setState(() {
_controller.reset();
dateIdeas.removeAt(index);
});
else
setState(() {
_controller.reset();
likes.add(dateIdeas[index]['Description']);
dateIdeas.removeAt(index);
});
},
child: new Column(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: FadeTransition(
opacity: _animation,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.pink[200],
width: 7,
),
borderRadius: BorderRadius.circular(9),
),
child: new Container(
child: new Image.asset(dateIdeas[index]['Image']),
),
),
),
),
Container(
alignment: Alignment.topCenter,
child: Text(dateIdeas[index]['Description'],
style: TextStyle(
fontSize: 30,
color: Colors.black,
fontFamily: 'IndieFlower',
)),
),
],
),
],
)
],
),
);
}
}
file.b:
import 'package:flutter/material.dart';
import './surprises.dart';
import './images.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SO Surprises',
theme: ThemeData(
primaryColor: Colors.pink[200],
),
home: MyHomePage(title: ''),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
{
int ideaCount = 1;
int _swipeCount = 0;
void _swipe() {
setState(() {
_swipeCount++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
Container(
height: 150,
width: 150,
child: IconButton(
icon: Image.asset('assets/images/SO.png'),
padding:
const EdgeInsets.only(right: 40.0, top: 10, bottom: 10.0),
),
),
GestureDetector(
onTap: () => print(Images.likes),
child: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Icon(
Icons.star,
color: Colors.white,// add custom icons also
),
),
),
],
),
body: _swipeCount == 0
? Stack(
children: <Widget>[
GestureDetector(
onHorizontalDragEnd: (DragEndDetails dragEndDetails) {
if(dragEndDetails.primaryVelocity == 0) return;
_swipe();
},
child: Container(
color: Colors.transparent,
alignment: Alignment.center,
child: Text("Swipe to get started! $_swipeCount"),
),
),
],
)
: Template(),
);
}
}
By prefixing your likes list with an underscore (_), it means you are making that method only accessible inside the class it belongs too.
To be able to use the method in other parts of your program, remove the underscore(_).
After removing the _, you are making the likes list accessible from other parts of your code.

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);
}
},
);