AspectRatio problem with Flutter camera and image layer - flutter

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.

Related

How to add button on top of image in flutter?

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"),
),
),
],
),

Changing the CameraPreview Aspect Ratio (Flutter)

I have an app where I have a Scaffold with an AppBar and a bottom Ads Banner.
In between, there is the CameraPreview from the camera plugin in Flutter.
As the CameraPreview is made to take the aspect ratio of the device/camera, the CameraPreview doesn't take the entire available space, leaving extra space on most devices.
I tried to crop the CameraPreview to show only whatever fits in the available space. It worked, but now the preview is stretched out
LayoutBuilder(
builder: (context, constraints) {
final cameraController = controller.cameraController!;
if(cameraController.value.previewSize != null) {
return ClipRect(
child: OverflowBox(
alignment: Alignment.center,
child: FittedBox(
fit: BoxFit.fitWidth,
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxWidth,
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
),
),
);
} else {
return const SizedBox.shrink();
}
},
)
I tried other solutions like Transform.scale, but that only zooms into the preview, it doesn't change the ratio or the stretching.
Looking solutions in the package itself doesn't help either, most similar issues are stalling or already closed for stalling.
What am I supposed to do here? Am I supposed to manually clip the preview's value?
check this below code,
Use get screen size by MediaQuery & calculate scale for aspect ratio widget and add CameraPreview() to it like below
// get screen size
final size = MediaQuery.of(context).size;
// calculate scale for aspect ratio widget
var scale = cameraController.value.aspectRatio / size.aspectRatio;
// check if adjustments are needed...
if (cameraController.value.aspectRatio < size.aspectRatio) {
scale = 1 / scale;
}
return Transform.scale(
scale: scale,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
);
Complete code
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (controller != null && controller.value.isRecordingVideo) {
//stop video
}
},
child: Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
body: Container(
child: Stack(
children: [
Positioned(
child: Container(
alignment: Alignment.center,
child: cameraScreen(),
),
),
Positioned(
child: Container(
alignment: Alignment.bottomCenter,
child: Container(
height: MediaQuery.of(context).size.height * .1,
color: Colors.black54,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//can add Controls
],
),
),
),
)
],
),
),
),
);
}
Widget cameraScreen() {
final CameraController cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black,
child: Center(
child: Text(
"Loading Camera...",
style: CameraTextStyle.cameraUtilLoadingStyle(),
),
),
);
} else {
return cameraWidget(context, cameraController);
}
}
Widget cameraWidget(context, cameraController) {
// get screen size
final size = MediaQuery.of(context).size;
// calculate scale for aspect ratio widget
var scale = cameraController.value.aspectRatio / size.aspectRatio;
// check if adjustments are needed...
if (cameraController.value.aspectRatio < size.aspectRatio) {
scale = 1 / scale;
}
return Transform.scale(
scale: scale,
child: Center(
child: AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
),
),
);
}
Widget cameraSwitch() {
final CameraController cameraController = controller;
return Container(
child: InkWell(
onTap: () {
if (cameraController != null &&
cameraController.value.isInitialized &&
!cameraController.value.isRecordingVideo) {
if (cameras.isNotEmpty) {
if (selectedCamera == cameras[0]) {
selectedCamera = cameras[1];
onNewCameraSelected(selectedCamera);
} else {
selectedCamera = cameras[0];
onNewCameraSelected(selectedCamera);
}
}
}
setState(() {});
},
child: Icon(
Icons.switch_camera,
size: 30,
color: Colors.white,
),
),
);
}

NoSuchMethodError---The method 'contains' was called on null. Tried calling contains(null)

I am writing code to get data and display it as list view on screen using flutter, but am getting NoSuchMethodError-- The method 'contains' was called on null. Tried calling contains(null). I thought the issue would be resolved by including the if statement(within the Widget getBody()), as, it points out specific actions to be taken if a certain state is returned, but had no luck.
here's the code for reference--
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:the_laptop_hub_3/app_screens/login.dart';
import 'addSuggestions.dart';
import 'home.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:http/http.dart' as http;
class UpdatesScreen extends StatefulWidget{
#override
_UpdatesScreenState createState() => _UpdatesScreenState();
}
class _UpdatesScreenState extends State<UpdatesScreen> {
List updatesList = [];
var _currentIndex = 0;
final tabs = [
Container(child:Home()),
Container(child:UpdatesScreen())
];
#override
void initState(){
super.initState();
fetchData();
}
fetchData()async{
var url = 'https://newsapi.org/v2/everything?domains=pcmag.com&sortBy=publishedAt&apiKey=b8010d33c3224b96b552b75eb026e1ca';
var response = await http.get(url);
if(response.statusCode == 200){
var updates = json.decode(response.body)['title'];
setState(() {
updatesList = updates;
});
}else{
setState(() {
updatesList = [];
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.teal,
title: Text(
"UPDATES SCREEN",
style: TextStyle(
),
),
actions: <Widget>[
IconButton(icon: Icon(Icons.logout),
onPressed: (){
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Login(),
));
}
)
],
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.black12, Colors.teal]
)
),
child: getBody()
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
currentIndex: _currentIndex,
items:[
BottomNavigationBarItem(
icon: Icon(Icons.laptop),
title: Text("Suggestions"),
backgroundColor: Colors.black45
),
BottomNavigationBarItem(
icon: Icon(Icons.update),
title: Text("Updates"),
backgroundColor: Colors.black45
),
],
onTap: (index){
setState(() {
_currentIndex = index;
});
},
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.transparent,
child: Icon(Icons.add, size: 50.0),
hoverElevation: 15.0,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30.0))
),
onPressed: (){
navigateToAddSuggestions(context);
},
),
);
}
void navigateToAddSuggestions(BuildContext context){
Navigator.push(
context,
MaterialPageRoute(
builder: (context)=> AddSuggestions(),
)
);
}
Widget getBody(){
if(updatesList.contains(null) || updatesList.length < 0 ){
return Center(child: CircularProgressIndicator(valueColor: new AlwaysStoppedAnimation<Color>(Colors.cyan),));
}
return ListView.builder(
itemCount: updatesList.length,
itemBuilder: (context, index)
{
return getCard(updatesList[index]);
});
}
Widget getCard(index){
var title = index['title'];
return Card(
child: Padding(
padding: EdgeInsets.all(10.0),
child: ListTile(
tileColor: Color(0xffadde6),
title: Row(
children: <Widget>[
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(60/2),
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage('https://i.pcmag.com/imagery/reviews/038Dr5TVEpwIv8rCljx6UcF-13..1588802180.jpg')
),
),
),
SizedBox(width: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
width: MediaQuery.of(context).size.width-140,
child: Text(title, style: TextStyle(fontSize: 13),),
),
],
),
],
),
),
),
);
}
}
You are getting this error because by the time your fetchData function completes and insert the response data in updatesList, the build methods executes and run getBody(). And inside getBody() Widget the first line it executes is
if(updatesList.contains(null) || updatesList.length < 0 ){
...
}
here updatesList.contains(null) condition gives you the error i.e " The method 'contains' was called on null." because till this point the value of updatesList is null and you called contain on a null value, therefore, you get that error.
So to solve this update your getBody() if statement to
if (commentList == null || commentList.isEmpty) {
return Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.cyan),
));
}
Also try integrating API using FutureBuilder, or StreamBuilder depending on the api.

Overlay pinched image above everything

I'm trying to overlay an image during max scaling (I'm using the class InteractiveViewer) on top of other objects (also the status bar). Basically like on Instagram. I couldn't find anything reading the docs. A hint on how to proceed?
child: InteractiveViewer(
transformationController: controller,
maxScale: 2.0,
minScale: 2.0,
child: imageBig,
fit: BoxFit.fitWidth,
),
According to this issue on flutter repository:
https://github.com/flutter/flutter/issues/66111
You can achive that by using OverlayEntry Class, which will handle the rendering of your InteractiveViewer child widget over the other widgets.
Also, you can find here a code snippet for InteractiveViewerOverlay widget, that you can use directly inside your project.
https://gist.github.com/zzterrozz/623531eef065a31470e85175c744c986
created by:
https://github.com/PixelToast
https://github.com/zzterrozz
Edited:
Here is an example for the InteractiveViewerOverlay widget and how to use it.
First, the InteractiveViewerOverlay widget
class InteractiveViewerOverlay extends StatefulWidget {
final Widget child;
final double maxScale;
const InteractiveViewerOverlay({
Key key,
#required this.child,
this.maxScale,
}) : super(key: key);
#override
_InteractiveViewerOverlayState createState() =>
_InteractiveViewerOverlayState();
}
class _InteractiveViewerOverlayState extends State<InteractiveViewerOverlay>
with SingleTickerProviderStateMixin {
var viewerKey = GlobalKey();
Rect placeholder;
OverlayEntry entry;
var controller = TransformationController();
Matrix4Tween snapTween;
AnimationController snap;
#override
void initState() {
super.initState();
snap = AnimationController(vsync: this);
snap.addListener(() {
if (snapTween == null) return;
controller.value = snapTween.evaluate(snap);
if (snap.isCompleted) {
entry.remove();
entry = null;
setState(() {
placeholder = null;
});
}
});
}
#override
void dispose() {
snap.dispose();
super.dispose();
}
Widget buildViewer(BuildContext context) {
return InteractiveViewer(
key: viewerKey,
transformationController: controller,
panEnabled: false,
maxScale: widget.maxScale ?? 2.5,
child: widget.child,
onInteractionStart: (details) {
if (placeholder != null) return;
setState(() {
var renderObject =
viewerKey.currentContext.findRenderObject() as RenderBox;
placeholder = Rect.fromPoints(
renderObject.localToGlobal(Offset.zero),
renderObject
.localToGlobal(renderObject.size.bottomRight(Offset.zero)),
);
});
entry = OverlayEntry(
builder: (context) {
return Positioned.fromRect(
rect: placeholder,
child: buildViewer(context),
);
},
);
Overlay.of(context).insert(entry);
},
onInteractionEnd: (details) {
snapTween = Matrix4Tween(
begin: controller.value,
end: Matrix4.identity(),
);
snap.value = 0;
snap.animateTo(
1,
duration: Duration(milliseconds: 250),
curve: Curves.ease,
);
});
}
#override
Widget build(BuildContext context) {
var viewer = placeholder != null
? SizedBox.fromSize(size: placeholder.size)
: buildViewer(context);
return Container(
child: viewer,
);
}
}
Next, An example of implementing the InteractiveViewerOverlay widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(),
body: ListView(children: [
Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
border:
Border(bottom: BorderSide(color: Colors.green))),
width: double.infinity,
height: 60,
child: Column(children: [
Text('Abdelazeem Kuratem',
style: TextStyle(color: Colors.black)),
Text('5 min', style: TextStyle(color: Colors.black)),
])),
InteractiveViewerOverlay(
child: Image.network(
"https://upload.wikimedia.org/wikipedia/commons/6/6a/Mona_Lisa.jpg",
fit: BoxFit.contain,
),
),
Container(
decoration: BoxDecoration(
color: Colors.grey[50],
border: Border(top: BorderSide(color: Colors.green))),
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_createBottomButton(
text: 'Like',
icon: Icons.thumb_up,
onPressed: () {}),
_createBottomButton(
text: 'Comment',
icon: Icons.comment,
onPressed: () {}),
_createBottomButton(
text: 'Share',
icon: Icons.share,
onPressed: () {}),
],
),
],
),
),
],
),
])),
);
}
Widget _createBottomButton({
String text,
IconData icon,
Null Function() onPressed,
}) {
return FlatButton.icon(
onPressed: onPressed,
icon: Icon(
icon,
color: Colors.green,
size: 21,
),
label: Text(
text,
style: TextStyle(color: Colors.green, fontSize: 14),
),
);
}
}

How to implement drag and drop with flutter

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?