I'm trying to load image from network and display it fully along with button on top of the image. To achieve this I looked up on various solution and found that this can be done using Stack widget. My implementation is as below
class DisplayImage extends StatefulWidget {
final String text;
DisplayImage({required this.text}) ;
#override
State<DisplayImage> createState() => _DisplayImageState();
}
class _DisplayImageState extends State<DisplayImage> {
#override
initState() {
// TODO: implement initState
_asyncMethod();
super.initState();
}
_asyncMethod() async {
Image.network(widget.text);
setState(() {
dataLoaded = true;
});
}
bool dataLoaded = false;
#override
Widget build(BuildContext context) {
if (dataLoaded){
return Scaffold(
backgroundColor: Colors.lightBlueAccent,
appBar: AppBar(title: Text("Selfie BGchanger"),centerTitle: true,
),
body: Center(child: Stack(
children: [Image.network(
widget.text,
fit: BoxFit.fill,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
const SizedBox(height: 50,),
Align(
alignment: Alignment(0, .2),
child: ElevatedButton(child: const Text('Save',style: TextStyle(fontWeight: FontWeight.normal)),style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
primary: Colors.black,
// padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20),
textStyle: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold)),
onPressed: () async{
String url = widget.text;
var imageId = await ImageDownloader.downloadImage(url);
if(imageId == null)
{return;}
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Saved to gallery!')));
Fluttertoast.showToast(msg: "Image saved to Gallery");
},
),
),
],),
),
);
} else {
return CircularProgressIndicator(backgroundColor: Colors.cyan,strokeWidth: 5,);
}
}
}
with this I get image is as below
save button is on top but what I'm trying to get is as below
Expected:
full sized image with save button on bottom center
I tried using boxfit.cover with height and width as infinit as below
fit: BoxFit.cover,
// height: double.infinity,
// width: double.infinity,
I got display error
How can I fix this to get expected image ? any help or suggestion on this will be highly appreciated
update:
based on answer suggestion I modified code as above and get output as below
Wrap your ElevatedButton widget with Positioned/Align widget.
Align(
alignment: Alignment(0, .2), //adjust based on your need
child: ElevatedButton(
Also you find more about Stack , Align widget.
body: Stack(
children: <Widget>[
Positioned.fill(
child: Image.network(
"",
fit: BoxFit.cover,
)),
Align(
alignment: Alignment(0, .2), // change .2 based on your need
child: ElevatedButton(
onPressed: () async {
await showDatePicker(
context: context,
initialEntryMode: DatePickerEntryMode.inputOnly,
initialDate: DateTime.now(),
firstDate: DateTime.now().subtract(Duration(days: 33)),
lastDate: DateTime.now().add(Duration(days: 33)),
);
},
child: Text("Dialog"),
),
),
],
),
Related
So I am implementing a 'settings' view in my Flutter app. The idea is all settings will appear in a ListView, and when the user will click on a ListTile, a showModalBottomSheet will pop where the user will be able to manipulate the setting. The only problem I am having is I am unable to migrate the showModalBottomSheet to a separate class as I cannot make the new function (outside the class) return the manipulated setting variable. This has lead to a messy code, all in a single class.
class Page extends StatefulWidget {
Page({Key key}) : super(key: key);
#override
_Page createState() => _Page();
}
class _Page extends State<Page> {
var value;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text("Age"),
trailing: Text(value),
onTap: () {
setState(() {
value = _valueSelector(); // This doesn't work, but to give an idea what I want
});
},
),
],
);
}
}
int _valueSelector(context) { // Doesn't return
var age = 0;
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Wrap(
children: [
Column(
children: <Widget>[
Slider(
value: age.toDouble(),
min: 0,
max: 18,
divisions: 18,
onChanged: (value) {
setState(() {
age = value.toInt();
});
},
),
],
),
],
);
});
},
).whenComplete(() {
return age; // Not sure if return is supposed to be here
});
}
How can I implement showModalBottomSheet in a separate class and just make it return the variable representing the setting chosen by the user?
You can try the below code,
First, create a class custom_bottom_sheet.dart and add the below code. You can use it everywhere in the project. And also use this library modal_bottom_sheet: ^0.2.0+1 to get the showMaterialModalBottomSheet.
customBottomSheet(BuildContext context, {#required Widget widget}) async {
return await showMaterialModalBottomSheet(
context: context,
backgroundColor: AppColors.transparent_100,
barrierColor: AppColors.black_75,
isDismissible: false,
enableDrag: true,
builder: (_, ScrollController scrollController) {
return widget;
},
);
}
Sample example code:
Create another class called bottom_sheet_example.dart and add the below code.
class BottomSheetExample {
static Future getSheet(BuildContext _context,
{ValueChanged<bool> onChanged}) async {
await customBottomSheet(
_context,
widget: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 40.0, right: 40.0),
height: 170.0,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(27.0),
topRight: Radius.circular(27.0))),
child: Container(
padding: EdgeInsets.only(top: 32),
child: Column(
children: [
Text("Were you at Queen Victoria Building?"),
SizedBox(height: 48),
Row(
children: [
Expanded(
child: RaisedButton(
child: Text("No"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(false);
},
),
),
SizedBox(width: 18),
Expanded(
child: RaisedButton(
child: Text("Yes"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(true);
},
),
),
],
),
SizedBox(height: 24),
],
),
),
)),
);
}
}
Button click to show the bottom sheet
#override
Widget build(BuildContext context) {
return Scaffold(
body: yourBodyWidget(),
bottomNavigationBar: Container(
height: 40,
width: double.infinity,
child: FlatButton(
onPressed: () {
/// call BottomSheetExample class
BottomSheetExample.getSheet(
context,
onChanged: (bool result) async {
///
/// add your code
},
);
},
child: Text("show bottom sheet")),
),
);
}
In onChanged callback you can return your value(obj/String/num/bool/list).
Thank you!
I have an AspectRatio problem with my Flutter camera (Plugin "camera_camera) and an image that I put on top of it with transparency as a layer.
I send you a screenshot of the problem. In the screenshot you can see the open camera and above it the picture I took right in front of it. Unfortunately you can see at different places that it does not match.
How do I get the camera to show exactly the same proportions as I photographed it from exactly the same position before?
If this helps: I recorded also a video with the issue:
https://danielederosa.de/downloads/flutter_issue.mp4
My Code
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
if (!controller.value.isInitialized) {
return Container(
color: theme.colorScheme.onPrimary,
child: Center(child: CircularProgressIndicator()));
}
return Scaffold(
appBar: CupertinoNavigationBar(
backgroundColor: theme.colorScheme.primary,
border: Border.symmetric(
vertical: BorderSide.none, horizontal: BorderSide.none),
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
Icons.chevron_left,
size: 30,
color: theme.colorScheme.onPrimary,
),
onPressed: () => Navigator.pop(context),
),
middle: Text("Memories",
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: theme.textTheme.headline3.fontSize)),
),
body: Container(
child: Column(
children: [
Expanded(
child: Camera(
mode: CameraMode.normal,
imageMask: lastPicture != null
? new Positioned.fill(
child: new Opacity(
opacity: 0.3,
child: RotatedBox(
quarterTurns: 1,
child: new Image.file(
File(lastPicture),
fit: BoxFit.cover,
),
),
),
)
: Container(),
onFile: (File file) {
_workWithImage(file);
},
),
),
],
),
),
);
}
I also tried to wrap the Camera widget into an AspectRatio widget with aspectRatio: 3/4 because my saved image are saved in this aspectRatio. But without success.
Do you have any idea to solve this issue?
I found a solution and got it to work.
Example code
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
var deviceSize = MediaQuery.of(context).size;
var sizeWidth = MediaQuery.of(context).size.width;
final deviceRatio = deviceSize.width / deviceSize.height;
var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
return Scaffold(
backgroundColor: theme.colorScheme.primary,
appBar: CupertinoNavigationBar(
backgroundColor: theme.colorScheme.primary,
border: Border.symmetric(
vertical: BorderSide.none, horizontal: BorderSide.none),
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
Icons.chevron_left,
size: 30,
color: theme.colorScheme.onPrimary,
),
onPressed: () => Navigator.pop(context),
),
middle: Text(APP_NAME,
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: theme.textTheme.headline3.fontSize)),
),
body: NativeDeviceOrientationReader(
useSensor: true,
builder: (context) {
NativeDeviceOrientation orientation =
NativeDeviceOrientationReader.orientation(context);
return Stack(children: [
FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return MeasureSize(
onChange: (size) {
setState(() {
cameraSize = size;
});
},
child: Transform.scale(
scale: cameraController.value.aspectRatio / deviceRatio,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: ClipRect(
child: OverflowBox(
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
width: sizeWidth,
height: sizeWidth /
cameraController.value.aspectRatio,
child: CameraPreview(
cameraController), // this is my CameraPreview
),
),
),
),
),
),
),
);
} else {
// Otherwise, display a loading indicator.
return Center(child: CircularProgressIndicator());
}
},
),
helpMode == true
? Transform.scale(
scale: cameraController.value.aspectRatio / deviceRatio,
child: Center(
child: Opacity(
opacity: .3,
child: orientation ==
NativeDeviceOrientation.landscapeLeft ||
orientation ==
NativeDeviceOrientation.landscapeRight
? RotatedBox(
quarterTurns: orientation ==
NativeDeviceOrientation.landscapeLeft
? 1
: 3,
child: Image.file(
File(lastPicture),
height: cameraSize.width,
fit: BoxFit.contain,
))
: Image.file(
File(lastPicture),
width: cameraSize.width,
height: cameraSize.height,
fit: BoxFit.contain,
),
),
),
)
: Container(),
]);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Container(
transform: Matrix4.translationValues(0.0, -8.0, 0.0),
child: FloatingActionButton(
backgroundColor: theme.colorScheme.primary,
child: Icon(
Icons.camera_alt,
color: theme.colorScheme.onPrimary,
),
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
//await _initializeControllerFuture;
// Construct the path where the image should be saved using the path
// package.
final path = join(
// Store the picture in the temp directory.
// Find the temp directory using the `path_provider` plugin.
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
// Attempt to take a picture and log where it's been saved.
await cameraController.takePicture(path);
_workWithImage(File(path));
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
),
),
);
}
}
typedef void OnWidgetSizeChange(Size size);
class MeasureSize extends StatefulWidget {
final Widget child;
final OnWidgetSizeChange onChange;
const MeasureSize({
Key key,
#required this.onChange,
#required this.child,
}) : super(key: key);
#override
_MeasureSizeState createState() => _MeasureSizeState();
}
class _MeasureSizeState extends State<MeasureSize> {
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
return Container(
key: widgetKey,
child: widget.child,
);
}
var widgetKey = GlobalKey();
var oldSize;
void postFrameCallback(_) {
var context = widgetKey.currentContext;
if (context == null) return;
var newSize = context.size;
if (oldSize == newSize) return;
oldSize = newSize;
widget.onChange(newSize);
}
}
I created the MeasureSize class. With this class I get the dimensions of a child widget. In this case I need the width and height from the camera. (Transform.scale) After I got this I had only to set this dimensions for the image overlay:
Image.file(
File(lastPicture),
width: cameraSize.width,
height: cameraSize.height,
fit: BoxFit.contain,
),
Now the image overlay fits to this what the camera displays.
I make one page to edit data by user.In this page I have two card.In the first card, I can see the user's picture.In the second card, I can see the user’s picture after selecting them from the phone or the picture they want to update.But now my problem is, how can I hide the first card if the second card appears after the user selects an image from the phone.I know it can be done by using Visibility in Java, but I'm a new filter developer and I don't know how to do it. I searched a lot and couldn't solve the problem.
Card 1:
child: Card(
child: new Column(
children: <Widget>[
Image.network(image, width: 385,height: 300,
fit: BoxFit.cover,
),
OutlineButton(
onPressed: chooseImage,
child: Text('Choose Image'),
),
])
),
Card 2:
Card(
child: SizedBox(
width: 400.0,
height: 300.0,
child: new Padding(
padding:
const EdgeInsets.only(top: 2.0, bottom: 2.0),
child: Expanded(flex: 1,
child: showImage(),
)
),
),
),
Full page:
class update_profilettt extends StatefulWidget {
var PostID;
update_profilettt({Key key, this.PostID}) : super(key: key);
#override
_update_profiletttState createState() => new _update_profiletttState(PostID);
}
class _update_profiletttState extends State<update_profilettt> {
MyPreferences _myPreferences = MyPreferences();
var PostID;
String uploadEndPoint;
_update_profiletttState(this. PostID);
Future<File> file;
String status = '';
String base64Image;
File tmpFile;
String errMessage = 'Error Uploading Image';
var data;
var _name = "";
// var _genderController = new TextEditingController();
var _nameController = new TextEditingController();
chooseImage() {
setState(() {
file = ImagePicker.pickImage(source: ImageSource.gallery);
});
setStatus('');
}
setStatus(String message) {
setState(() {
status = message;
});
}
startUpload() {
setStatus('Uploading Image...');
if (null == tmpFile) {
setStatus(errMessage);
return;
}
String NameImage =DateTime.now().millisecondsSinceEpoch.toString();
upload(NameImage);
}
upload(String NameImage) {
uploadEndPoint = 'http://xxxxxxx/up.php?id='+widget.PostID.toString();
print('yeyyyyddyy $uploadEndPoint');
http.post(uploadEndPoint, body: {
'id': widget.PostID.toString(),
}).then((result) {
setStatus(result.statusCode == 200 ? result.body : errMessage);
}).catchError((error) {
setStatus(error);
});
}
Widget showImage() {
return FutureBuilder<File>(
future: file,
builder: (BuildContext context, AsyncSnapshot<File> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
null != snapshot.data) {
tmpFile = snapshot.data;
base64Image = base64Encode(snapshot.data.readAsBytesSync());
return Flexible(
child: Card(
margin:EdgeInsets.all(10) ,
child: Image.file(
snapshot.data,
fit: BoxFit.cover,
),
),
);
} else if (null != snapshot.error) {
return const Text(
'Error Picking Image',
textAlign: TextAlign.center,
);
} else {
return const Text(
'',
textAlign: TextAlign.center,
);
}
},
);
}
Future<String> _ShowDialog(String msg) async {
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('Rewind and remember'),
content: new SingleChildScrollView(
child: new ListBody(
children: <Widget>[
new Text(msg),
],
),
),
actions: <Widget>[
new FlatButton(
child: new Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
void _editData() async {
// String NameImage =DateTime.now().millisecondsSinceEpoch.toString();
var url = 'http://xxxxxxxxxx/up.php?id='+widget.PostID.toString();
var response = await http.post(url, body: {
'id': widget.PostID.toString(),
// "id": _userController.text,
"name": _nameController.text,
"image": base64Image,
// "gender": _genderController.text,
});
if (response.statusCode == 200) {
_ShowDialog("Updated Successfully");
} else {
_ShowDialog("Updated Failer");
}
//onEditedAccount();
//print(_adresseController.text);
}
_fetchData() async {final url = "http://xxxxxxxx/nhy.php?id=${widget.PostID}";
final response = await http.get(url);
if (response.statusCode == 200) {
final map = json.decode(response.body);
final videosMap = map["result"];
setState(() {
this.data = videosMap;
_name = data[0]['name'];
image = data[0]['image'];
// _gender = data[0]['gender'];
print(data);
});
}
}
#override
void initState() {
super.initState();
_fetchData();
}
#override
Widget build(BuildContext context) {
_nameController= new TextEditingController(text: _name);
if(chooseImage !=null){
}
return new Scaffold(
appBar: AppBar(
title: Text("Edit Post"),
),
body: new Center(
child: data == null
? new CircularProgressIndicator()
: new ListView(
children: <Widget>[
new Padding(
padding: const EdgeInsets.fromLTRB(5, 10, 5, 5),
child: Column(
children: <Widget>[
new Padding(
padding:
const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Expanded(flex: 1,
child: Container(
child: Card(
child: new Column(
children: <Widget>[
Image.network(image, width: 385,height: 300,
fit: BoxFit.cover,
),
OutlineButton(
onPressed: chooseImage,
child: Text('Choose Image'),
),
])
),
),
),
),
Card(
child: SizedBox(
width: 400.0,
height: 300.0,
child: new Padding(
padding:
const EdgeInsets.only(top: 2.0, bottom: 2.0),
child: Expanded(flex: 1,
child: showImage(),
)
),
),
),
Card (
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
),
Container(
margin: EdgeInsets.all(4),
child: TextField(
maxLength: 10,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Name',
filled: true,
hintText: ''),
controller: _nameController,
),
),
SizedBox(
height: 5.0,
),
),
SizedBox(
height: 5.0,
),
),
),
),
),
SizedBox(
height: 5.0,
),
]
)
),
SizedBox(
width: double.infinity,
child: new FlatButton(
child: const Text('Update'),color: Colors.amber,
padding: EdgeInsets.fromLTRB(100, 18, 100, 18),
onPressed: () { _editData();
},
),
),
SizedBox(
height: 10.0,
),
],
),
)
],
),
));
}
}
There is a Visibility widget in flutter too you can wrap your card with it
Example
bool visibilityController = true;
true for visibile and false for not visible
so when select the card use setstate to toggle it.
setState(() {
});
Visibility(
visible: visibilityController,
child : //Your card
),
Hope this is what you wished for.
For your code
you can do this when your showimage() gets an image
Widget showImage() {
return FutureBuilder<File>(
future: file,
builder: (BuildContext context, AsyncSnapshot<File> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
null != snapshot.data) {
tmpFile = snapshot.data;
base64Image = base64Encode(snapshot.data.readAsBytesSync());
setState(() {
// added here
visibilityController = false;
});
return ........ your code
I am attempting to show videos in a listview that is preventing me from declaring the videocontroller in the initState. This causes me to accidentally be redrawing the video multiple times during the application. I am receiving this error:
FATAL EXCEPTION: ExoPlayerImplInternal:Handler
then
java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available
with my current implementation. It appears to work fora while but the memory slowly builds up until it is full. How can I implement this differently?
here is the code I am calling in the stream:
Widget getVideoItem(DocumentSnapshot doc) {
if (watchList.contains(doc['user'])) watched = true;
DateTime dateTime = DateTime.parse(doc['time']);
_videoPlayerController = CachedVideoPlayerController.network(doc["downUrl"])
..initialize();
_videoPlayerController.setLooping(true);
_videoPlayerController.play();
volumeOn = sharedPreferences.getBool("vidVol");
if (volumeOn == null) {
sharedPreferences.setBool("vidVol", false);
volumeOn = false;
}
if (volumeOn) {
_videoPlayerController.setVolume(1.0);
} else {
_videoPlayerController.setVolume(0.0);
}
return new FutureBuilder(
future: getUserData(doc["user"]),
builder: (BuildContext context, snapshot) {
return SizedBox(
height: MediaQuery.of(context).size.width + 140,
width: MediaQuery.of(context).size.width,
child: Column(children: <Widget>[
new ListTile(
title: new Text(userInfo),
subtitle: new Text(doc["title"]),
leading: FutureBuilder(
future: getProfUrl(doc),
builder: (BuildContext context, snapshot) {
Widget child;
if (!snapshot.hasData) {
child = _showCircularProgress();
} else {
child = child = new Container(
width: 44.0,
height: 44.0,
child: CachedNetworkImage(
imageUrl: doc["profUrl"],
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
),
);
}
return child;
}),
),
new Padding(
padding: EdgeInsets.fromLTRB(4, 4, 4, 4),
child: FutureBuilder(
future: getDownUrl(doc),
builder: (BuildContext context, snapshot) {
List<Widget> children;
if (!snapshot.hasData) {
children = [_showCircularProgress()];
} else {
children = [
Center(
child: new AspectRatio(
aspectRatio: 1 / 1,
child: Stack(
children: [
VisibilityDetector(
key: Key("unique key"),
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction > .20) {
_videoPlayerController.pause();
} else {
_videoPlayerController.play();
}
},
child: CachedVideoPlayer(
_videoPlayerController,
)),
IconButton(
icon: volumeOn
? Icon(Icons.volume_up)
: Icon(Icons.volume_off),
onPressed: () {
setState(() {
_videoPlayerController.pause();
sharedPreferences.setBool(
"vidVol", !volumeOn);
});
},
),
],
),
),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
}),
),
new Row(
children: [
new IconButton(
icon: !watched
? new Icon(
Icons.remove_red_eye,
color: Colors.black26,
)
: new Icon(
Icons.remove_red_eye,
color: Colors.blueGrey[400],
),
onPressed: () {
initToggleWatched(watchList, doc["user"], name, position,
secPosition, state, year, user);
}),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
dateTime.day.toString() +
"/" +
dateTime.month.toString() +
"/" +
dateTime.year.toString(),
style: TextStyle(color: Colors.black26, fontSize: 12),
),
),
),
],
)
]),
);
},
);
}
Try making the widget with a controller a separate StatefullWidget instead of putting everything in one place and manage the instantiation and disposal of the controller in the initState() and dispose() methods.
How can I move my container or any other widgets on flutter around the screen and drop at some locations?
I found flutter widgets Draggable and DragTarget. How to use them to implement the drag and drop?
Draggable and DragTarget allow us to drag a widget across the screen.
A Draggable widgets gives the ability to move to any other widget while the DragTarget acts as the sink or drop location for a Draggable widget.
Find the below code sample using which I implemented a simple odd-or-even game
Hell yeah, I'm a Game Developer ◕‿↼
import 'package:flutter/material.dart';
import 'dart:math';
class OddOrEven extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _OddOrEvenState();
}
}
class _OddOrEvenState extends State<OddOrEven> {
bool accepted = false;
Color dotColor = Colors.blue;
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey();
int val = 0;
int score = 0;
#override
Widget build(BuildContext context) {
// assign a random number to value which will be used as the box value
val = Random().nextInt(100);
return Scaffold(
key: scaffoldKey,
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
// just a score and mock player name indicator
Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: Center(
child: Chip(
avatar: CircleAvatar(
backgroundColor: Colors.teal,
child: Text(
score.toString(),
style: TextStyle(color: Colors.white),
),
),
label: Text(
'Player Alpha',
style: TextStyle(
fontSize: 20.0,
color: Colors.black,
fontStyle: FontStyle.italic),
),
),
),
),
),
// here comes our draggable.
// it holds data which is our random number
// the child of the draggable is a container reactangural in shape and
//
Draggable(
data: val,
child: Container(
width: 100.0,
height: 100.0,
child: Center(
child: Text(
val.toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
color: Colors.pink,
),
// This will be displayed when the widget is being dragged
feedback: Container(
width: 100.0,
height: 100.0,
child: Center(
child: Text(
val.toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
color: Colors.pink,
),
// You can also specify 'childWhenDragging' option to draw
// the original widget changes at the time of drag.
),
// and here this row holds our two DragTargets.
// One for odd numbers and the other for even numbers.
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: 100.0,
height: 100.0,
color: Colors.green,
// Even holder DragTarget
//
child: DragTarget(
builder: (context, List<int> candidateData, rejectedData) {
print(candidateData);
return Center(
child: Text(
"Even",
style: TextStyle(color: Colors.white, fontSize: 22.0),
));
},
// On will accept gets called just before it accepts the drag source.
// if needed, we can reject the data here. But we are not doing that as this is a GAME !!! :)
onWillAccept: (data) {
print("Will accpt");
return true; //return false to reject it
},
// On accepting the data by the DragTarget we simply check whether the data is odd or even and accept based on that and increment the counter and rebuild the widget tree for a new random number at the source.
onAccept: (data) {
print("On accpt");
if (data % 2 == 0) {
setState(() {
score++;
});
// How did you manage to score 3 points😮
// Congrats. You won the game.
if (score >= 3) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("Congrats!!"),
content: Text("No-brainer...😮"),
actions: <Widget>[
FlatButton(
child: Text("Ok."),
onPressed: () {
Navigator.of(context).pop();
setState(() {
score = 0;
});
},
)
],
);
});
}
} else {
setState(() {});
}
},
),
),
// And here is the Odd-holder
Container(
width: 100.0,
height: 100.0,
color: Colors.deepPurple,
child: DragTarget(
builder: (context, List<int> candidateData, rejectedData) {
return Center(
child: Text(
"Odd",
style: TextStyle(color: Colors.white, fontSize: 22.0),
));
},
onWillAccept: (data) {
return true;
},
onAccept: (data) {
if (data % 2 != 0) {
setState(() {
score++;
});
if (score >= 10) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("Congrats!!"),
content: Text("No-brainer...😮"),
actions: <Widget>[
FlatButton(
child: Text("Thanks"),
onPressed: () {
Navigator.of(context).pop();
setState(() {
score = 0;
});
},
)
],
);
});
}
} else {
setState(() {});
}
},
),
)
],
)
],
),
),
);
}
}
If you need to drop at a non-fixed location (Draggable without a DragTarget), this can also be implemented with Stack()/Positioned() using renderbox sizing, as per How to move element anywhere inside parent container with drag and drop in Flutter?