I have an image in which certain items are there which have starting offsets and their height and width, corresponding to each item in images I have different text, I have to drag the text and drop it to the correct position on the image,I am getting different offset while tap to particular location and while drag also I am getting different offsets. How can I get the same offsets?
Here is my code and image I am using. these are the details of Tree
x=673
y=635
h=214
w=149
with respect to image.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
ImageInfo _imageInfo;
AssetImage assestImage;
double dx;
double dy;
Offset dragOffset;
#override
void initState() {
super.initState();
assestImage = AssetImage('assets/hospital.jpg');
WidgetsBinding.instance.addPostFrameCallback((a) => _getImageInfo());
}
void _getImageInfo() async {
Image image = new Image.asset('assets/hospital.jpg');
image.image
.resolve(new ImageConfiguration())
.addListener((ImageInfo info, bool _) {
_imageInfo = info;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
DragTarget(
onAccept: (Offset dragOffset) {},
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return TapImage(
onTap: (Offset offset) {
print('Offset on Tapping the image is $offset');
dx = offset.dx * _imageInfo.image.width;
dy = offset.dy * _imageInfo.image.height;
if (_imageInfo != null) {
print('Image clicked: ${dx.toInt()} x ${dy.toInt()}');
if ((673 <= dx && dx <= 822) &&
(635 <= dy && dy <= 849)) {
print('you drop in tree');
} else {}
}
},
image: assestImage,
);
}),
Draggable(
dragAnchor: DragAnchor.pointer,
onDragEnd: (details) {
setState(() {
dragOffset = details.offset;
});
print('dragoffset in onDrag Method is $dragOffset');
},
data: dragOffset,
child: Container(
color: Colors.green,
child: Text(
'Tree',
style: TextStyle(fontSize: 30.0),
)),
feedback: Container(
height: 10.0,
child: Text(
'Tree',
style: TextStyle(fontSize: 15.0),
),
)),
],
),
),
);
}
}
typedef void OnTapLocation(Offset offset);
class TapImage extends StatelessWidget {
TapImage({Key key, this.onTap, this.image}) : super(key: key);
final OnTapLocation onTap;
final ImageProvider image;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (TapDownDetails details) => _onTap(details, context),
onLongPress: () {},
child: Image(image: AssetImage('assets/hospital.jpg')),
);
}
void _onTap(TapDownDetails details, BuildContext context) {
RenderBox getBox = context.findRenderObject();
Offset local = getBox.globalToLocal(details.globalPosition);
print('locla ois $local');
onTap(Offset(local.dx / getBox.size.width, local.dy / getBox.size.height));
}
}
its working for me
class _HomeState extends State<Home> {
ImageInfo _imageInfo;
AssetImage assestImage;
double getheight;
double getywidth;
Offset dragOffset;
#override
void initState() {
super.initState();
assestImage = AssetImage('assets/hospital.jpg');
WidgetsBinding.instance.addPostFrameCallback((a) => _getImageInfo());
}
void _getImageInfo() async {
Image image = new Image.asset('assets/hospital.jpg');
image.image
.resolve(new ImageConfiguration())
.addListener((ImageInfo info, bool _) {
_imageInfo = info;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: <Widget>[
TapImage(
onTap: (Offset offset, RenderBox getBox, TapDownDetails details) {
double dx;
double dy;
dx = offset.dx * _imageInfo.image.width;
dy = offset.dy * _imageInfo.image.height;
setState(() {
dragEnd(dx, dy);
});
},
image: assestImage,
),
Draggable(
dragAnchor: DragAnchor.pointer,
onDragStarted: () {
WidgetsBinding.instance
.addPostFrameCallback((_) => setState(() {
RenderBox getBox = context.findRenderObject();
getheight = getBox.size.height;
getywidth = getBox.size.width;
}));
},
onDragEnd: (details) {
double dx;
double dy;
dx = (details.offset.dx / getywidth) * _imageInfo.image.width;
dy =
((details.offset.dy) / getywidth) * _imageInfo.image.height;
setState(() {
dragEnd(dx, dy);
});
},
child: Padding(
padding: const EdgeInsets.only(top: 28.0),
child: Container(
color: Colors.green,
child: Text(
'tree',
style: TextStyle(fontSize: 30.0),
)),
),
feedback: Container(
height: 10.0,
child: Text(
'tree',
style: TextStyle(fontSize: 15.0),
),
)),
],
)),
);
}
void dragEnd(double dx, double dy) {
if (_imageInfo != null) {
if ((673 <= dx && dx <= 822) && (635 <= dy && dy <= 849)) {
showDialog(
context: context,
builder: (context) {
return _textDescriptionDialog(
context,
'Drag on tree',
);
},
);
} else {
showDialog(
context: context,
builder: (context) {
return _textDescriptionDialog(
context,
'Drag outside',
);
},
);
}
}
}
Widget _textDescriptionDialog(BuildContext context, String text) {
return new FractionallySizedBox(
heightFactor: MediaQuery.of(context).orientation == Orientation.portrait
? 0.5
: 0.8,
widthFactor: MediaQuery.of(context).orientation == Orientation.portrait
? 0.8
: 0.4,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
),
child: Container(child: Center(child: Text(text))),
));
}
}
typedef void OnTapLocation(
Offset offset, RenderBox getBox, TapDownDetails details);
class TapImage extends StatelessWidget {
TapImage({Key key, this.onTap, this.image}) : super(key: key);
final OnTapLocation onTap;
final ImageProvider image;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (TapDownDetails details) => _onTap(details, context),
child: Image(image: AssetImage('assets/hospital.jpg')),
);
}
void _onTap(TapDownDetails details, BuildContext context) {
RenderBox getBox = context.findRenderObject();
print('size is ${getBox.size}');
Offset local = getBox.globalToLocal(details.globalPosition);
print('local is $local');
onTap(Offset(local.dx / getBox.size.width, local.dy / getBox.size.height),
getBox, details);
}
}
You could make new Widget then get local render box size. Something like this:
class _MyHomePageState extends State<MyHomePage> {
NetworkImage _networkImage;
ImageInfo _imageInfo;
#override
void initState() {
super.initState();
_networkImage = NetworkImage('https://i.stack.imgur.com/2PnTa.jpg');
_getImageInfo();
}
void _getImageInfo() async {
NetworkImage _key = await _networkImage.obtainKey(ImageConfiguration());
_networkImage.load(_key).addListener((ImageInfo i, bool b){
print('Image size: ${i.image.width} - ${i.image.height}');
_imageInfo = i;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ImageDetector(
onTap: (Offset offset){
if(_imageInfo != null){
print('Image clicked: ${offset.dx * _imageInfo.image.width} x ${offset.dy * _imageInfo.image.height}');
}
},
image: _networkImage,
),
),
);
}
}
typedef void OnTapLocation(Offset offset);
class ImageDetector extends StatelessWidget {
ImageDetector({Key key, this.onTap, this.image}) : super(key: key);
final OnTapLocation onTap;
final ImageProvider image;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (TapDownDetails details) => _onTap(details, context),
child: Image(image: image),
);
}
void _onTap(TapDownDetails details, BuildContext context) {
RenderBox getBox = context.findRenderObject();
Offset local = getBox.globalToLocal(details.globalPosition);
print('Clicked on: ${local.dx / getBox.size.width} - ${local.dy / getBox.size.height}');
onTap(Offset(local.dx / getBox.size.width, local.dy / getBox.size.height));
}
}
This will return click position between 0.0, 0.0 and 1.0, 1.0, you can get size of the image and get exact location from those.
Edit: updated the code
Related
I'm developing a mobile app using the Flutter framework.
I need to read QR Codes, and I have successfully implemented the Barcode Scan library, based on ZXing to decode one through the camera.
Now I would also like to add the chance to pick an image containing a QR code from the gallery and decoding, without having to go through the camera.
I checked the library I'm using and also this one without finding any reference to such functionality: qrcode_reader, qr.
but in vain.
A solution that would imply serializing and decoding byte by byte an image using pure Dart would be acceptable as well.
As I suggested you in my comment you could try using firebase_ml_visionpackage.
Always remember:
You must also configure Firebase for each platform project: Android
and iOS (see the example folder or
https://codelabs.developers.google.com/codelabs/flutter-firebase/#4
for step by step details).
In this example (taken from the official one, but with a specific plugin version - not the latest one) we use image_picker plugin to get the image from device and then we decode the QRCode.
pubspec.yaml
firebase_ml_vision: 0.2.1
image_picker: 0.4.12+1
detector_painters.dart
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/material.dart';
enum Detector { barcode, face, label, cloudLabel, text }
class BarcodeDetectorPainter extends CustomPainter {
BarcodeDetectorPainter(this.absoluteImageSize, this.barcodeLocations);
final Size absoluteImageSize;
final List<Barcode> barcodeLocations;
#override
void paint(Canvas canvas, Size size) {
final double scaleX = size.width / absoluteImageSize.width;
final double scaleY = size.height / absoluteImageSize.height;
Rect scaleRect(Barcode barcode) {
return Rect.fromLTRB(
barcode.boundingBox.left * scaleX,
barcode.boundingBox.top * scaleY,
barcode.boundingBox.right * scaleX,
barcode.boundingBox.bottom * scaleY,
);
}
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
for (Barcode barcode in barcodeLocations) {
paint.color = Colors.green;
canvas.drawRect(scaleRect(barcode), paint);
}
}
#override
bool shouldRepaint(BarcodeDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.barcodeLocations != barcodeLocations;
}
}
class FaceDetectorPainter extends CustomPainter {
FaceDetectorPainter(this.absoluteImageSize, this.faces);
final Size absoluteImageSize;
final List<Face> faces;
#override
void paint(Canvas canvas, Size size) {
final double scaleX = size.width / absoluteImageSize.width;
final double scaleY = size.height / absoluteImageSize.height;
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..color = Colors.red;
for (Face face in faces) {
canvas.drawRect(
Rect.fromLTRB(
face.boundingBox.left * scaleX,
face.boundingBox.top * scaleY,
face.boundingBox.right * scaleX,
face.boundingBox.bottom * scaleY,
),
paint,
);
}
}
#override
bool shouldRepaint(FaceDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.faces != faces;
}
}
class LabelDetectorPainter extends CustomPainter {
LabelDetectorPainter(this.absoluteImageSize, this.labels);
final Size absoluteImageSize;
final List<Label> labels;
#override
void paint(Canvas canvas, Size size) {
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(
ui.ParagraphStyle(
textAlign: TextAlign.left,
fontSize: 23.0,
textDirection: TextDirection.ltr),
);
builder.pushStyle(ui.TextStyle(color: Colors.green));
for (Label label in labels) {
builder.addText('Label: ${label.label}, '
'Confidence: ${label.confidence.toStringAsFixed(2)}\n');
}
builder.pop();
canvas.drawParagraph(
builder.build()
..layout(ui.ParagraphConstraints(
width: size.width,
)),
const Offset(0.0, 0.0),
);
}
#override
bool shouldRepaint(LabelDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.labels != labels;
}
}
// Paints rectangles around all the text in the image.
class TextDetectorPainter extends CustomPainter {
TextDetectorPainter(this.absoluteImageSize, this.visionText);
final Size absoluteImageSize;
final VisionText visionText;
#override
void paint(Canvas canvas, Size size) {
final double scaleX = size.width / absoluteImageSize.width;
final double scaleY = size.height / absoluteImageSize.height;
Rect scaleRect(TextContainer container) {
return Rect.fromLTRB(
container.boundingBox.left * scaleX,
container.boundingBox.top * scaleY,
container.boundingBox.right * scaleX,
container.boundingBox.bottom * scaleY,
);
}
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
for (TextBlock block in visionText.blocks) {
for (TextLine line in block.lines) {
for (TextElement element in line.elements) {
paint.color = Colors.green;
canvas.drawRect(scaleRect(element), paint);
}
paint.color = Colors.yellow;
canvas.drawRect(scaleRect(line), paint);
}
paint.color = Colors.red;
canvas.drawRect(scaleRect(block), paint);
}
}
#override
bool shouldRepaint(TextDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.visionText != visionText;
}
}
main.dart
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'detector_painters.dart';
void main() => runApp(MaterialApp(home: _MyHomePage()));
class _MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<_MyHomePage> {
File _imageFile;
Size _imageSize;
dynamic _scanResults;
String _title = 'ML Vision Example';
Detector _currentDetector = Detector.text;
Future<void> _getAndScanImage() async {
setState(() {
_imageFile = null;
_imageSize = null;
});
final File imageFile =
await ImagePicker.pickImage(source: ImageSource.gallery);
if (imageFile != null) {
_getImageSize(imageFile);
_scanImage(imageFile);
}
setState(() {
_imageFile = imageFile;
});
}
Future<void> _getImageSize(File imageFile) async {
final Completer<Size> completer = Completer<Size>();
final Image image = Image.file(imageFile);
image.image.resolve(const ImageConfiguration()).addListener(
(ImageInfo info, bool _) {
completer.complete(Size(
info.image.width.toDouble(),
info.image.height.toDouble(),
));
},
);
final Size imageSize = await completer.future;
setState(() {
_imageSize = imageSize;
});
}
Future<void> _scanImage(File imageFile) async {
setState(() {
_scanResults = null;
});
final FirebaseVisionImage visionImage =
FirebaseVisionImage.fromFile(imageFile);
FirebaseVisionDetector detector;
switch (_currentDetector) {
case Detector.barcode:
detector = FirebaseVision.instance.barcodeDetector();
break;
case Detector.face:
detector = FirebaseVision.instance.faceDetector();
break;
case Detector.label:
detector = FirebaseVision.instance.labelDetector();
break;
case Detector.cloudLabel:
detector = FirebaseVision.instance.cloudLabelDetector();
break;
case Detector.text:
detector = FirebaseVision.instance.textRecognizer();
break;
default:
return;
}
final dynamic results =
await detector.detectInImage(visionImage) ?? <dynamic>[];
setState(() {
_scanResults = results;
if (results is List<Barcode>
&& results[0] is Barcode) {
Barcode res = results[0];
_title = res.displayValue;
}
});
}
CustomPaint _buildResults(Size imageSize, dynamic results) {
CustomPainter painter;
switch (_currentDetector) {
case Detector.barcode:
painter = BarcodeDetectorPainter(_imageSize, results);
break;
case Detector.face:
painter = FaceDetectorPainter(_imageSize, results);
break;
case Detector.label:
painter = LabelDetectorPainter(_imageSize, results);
break;
case Detector.cloudLabel:
painter = LabelDetectorPainter(_imageSize, results);
break;
case Detector.text:
painter = TextDetectorPainter(_imageSize, results);
break;
default:
break;
}
return CustomPaint(
painter: painter,
);
}
Widget _buildImage() {
return Container(
constraints: const BoxConstraints.expand(),
decoration: BoxDecoration(
image: DecorationImage(
image: Image.file(_imageFile).image,
fit: BoxFit.fill,
),
),
child: _imageSize == null || _scanResults == null
? const Center(
child: Text(
'Scanning...',
style: TextStyle(
color: Colors.green,
fontSize: 30.0,
),
),
)
: _buildResults(_imageSize, _scanResults),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
actions: <Widget>[
PopupMenuButton<Detector>(
onSelected: (Detector result) {
_currentDetector = result;
if (_imageFile != null) _scanImage(_imageFile);
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<Detector>>[
const PopupMenuItem<Detector>(
child: Text('Detect Barcode'),
value: Detector.barcode,
),
const PopupMenuItem<Detector>(
child: Text('Detect Face'),
value: Detector.face,
),
const PopupMenuItem<Detector>(
child: Text('Detect Label'),
value: Detector.label,
),
const PopupMenuItem<Detector>(
child: Text('Detect Cloud Label'),
value: Detector.cloudLabel,
),
const PopupMenuItem<Detector>(
child: Text('Detect Text'),
value: Detector.text,
),
],
),
],
),
body: _imageFile == null
? const Center(child: Text('No image selected.'))
: _buildImage(),
floatingActionButton: FloatingActionButton(
onPressed: _getAndScanImage,
tooltip: 'Pick Image',
child: const Icon(Icons.add_a_photo),
),
);
}
}
UPDATE for iOS and Android
to address a successful build on iOS I've got to use an even lower version of firebase_ml_vision plugin otherwise you have this error.
pubspec.yaml
# https://github.com/firebase/firebase-ios-sdk/issues/2151
firebase_ml_vision: 0.1.2
image_picker: 0.4.12+1
And I get the error you have so I've got to modify also my classes.
main.dart
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'detector_painters.dart';
void main() => runApp(MaterialApp(home: _MyHomePage()));
class _MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<_MyHomePage> {
File _imageFile;
Size _imageSize;
dynamic _scanResults;
String _title = 'ML Vision Example';
Detector _currentDetector = Detector.barcode;
Future<void> _getAndScanImage() async {
setState(() {
_imageFile = null;
_imageSize = null;
});
final File imageFile =
await ImagePicker.pickImage(source: ImageSource.gallery);
if (imageFile != null) {
_getImageSize(imageFile);
_scanImage(imageFile);
}
setState(() {
_imageFile = imageFile;
});
}
Future<void> _getImageSize(File imageFile) async {
final Completer<Size> completer = Completer<Size>();
final Image image = Image.file(imageFile);
image.image.resolve(const ImageConfiguration()).addListener(
(ImageInfo info, bool _) {
completer.complete(Size(
info.image.width.toDouble(),
info.image.height.toDouble(),
));
},
);
final Size imageSize = await completer.future;
setState(() {
_imageSize = imageSize;
});
}
Future<void> _scanImage(File imageFile) async {
setState(() {
_scanResults = null;
});
final FirebaseVisionImage visionImage =
FirebaseVisionImage.fromFile(imageFile);
FirebaseVisionDetector detector;
switch (_currentDetector) {
case Detector.barcode:
detector = FirebaseVision.instance.barcodeDetector();
break;
case Detector.face:
detector = FirebaseVision.instance.faceDetector();
break;
case Detector.label:
detector = FirebaseVision.instance.labelDetector();
break;
default:
return;
}
final dynamic results =
await detector.detectInImage(visionImage) ?? <dynamic>[];
setState(() {
_scanResults = results;
if (results is List<Barcode>
&& results[0] is Barcode) {
Barcode res = results[0];
_title = res.displayValue;
}
});
}
CustomPaint _buildResults(Size imageSize, dynamic results) {
CustomPainter painter;
switch (_currentDetector) {
case Detector.barcode:
painter = BarcodeDetectorPainter(_imageSize, results);
break;
case Detector.face:
painter = FaceDetectorPainter(_imageSize, results);
break;
case Detector.label:
painter = LabelDetectorPainter(_imageSize, results);
break;
case Detector.cloudLabel:
painter = LabelDetectorPainter(_imageSize, results);
break;
default:
break;
}
return CustomPaint(
painter: painter,
);
}
Widget _buildImage() {
return Container(
constraints: const BoxConstraints.expand(),
decoration: BoxDecoration(
image: DecorationImage(
image: Image.file(_imageFile).image,
fit: BoxFit.fill,
),
),
child: _imageSize == null || _scanResults == null
? const Center(
child: Text(
'Scanning...',
style: TextStyle(
color: Colors.green,
fontSize: 30.0,
),
),
)
: _buildResults(_imageSize, _scanResults),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
actions: <Widget>[
PopupMenuButton<Detector>(
onSelected: (Detector result) {
_currentDetector = result;
if (_imageFile != null) _scanImage(_imageFile);
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<Detector>>[
const PopupMenuItem<Detector>(
child: Text('Detect Barcode'),
value: Detector.barcode,
),
const PopupMenuItem<Detector>(
child: Text('Detect Face'),
value: Detector.face,
),
const PopupMenuItem<Detector>(
child: Text('Detect Label'),
value: Detector.label,
),
const PopupMenuItem<Detector>(
child: Text('Detect Cloud Label'),
value: Detector.cloudLabel,
),
const PopupMenuItem<Detector>(
child: Text('Detect Text'),
value: Detector.text,
),
],
),
],
),
body: _imageFile == null
? const Center(child: Text('No image selected.'))
: _buildImage(),
floatingActionButton: FloatingActionButton(
onPressed: _getAndScanImage,
tooltip: 'Pick Image',
child: const Icon(Icons.add_a_photo),
),
);
}
}
detector_painters.dart
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/material.dart';
enum Detector { barcode, face, label, cloudLabel, text }
class BarcodeDetectorPainter extends CustomPainter {
BarcodeDetectorPainter(this.absoluteImageSize, this.barcodeLocations);
final Size absoluteImageSize;
final List<Barcode> barcodeLocations;
#override
void paint(Canvas canvas, Size size) {
final double scaleX = size.width / absoluteImageSize.width;
final double scaleY = size.height / absoluteImageSize.height;
Rect scaleRect(Barcode barcode) {
return Rect.fromLTRB(
barcode.boundingBox.left * scaleX,
barcode.boundingBox.top * scaleY,
barcode.boundingBox.right * scaleX,
barcode.boundingBox.bottom * scaleY,
);
}
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
for (Barcode barcode in barcodeLocations) {
paint.color = Colors.green;
canvas.drawRect(scaleRect(barcode), paint);
}
}
#override
bool shouldRepaint(BarcodeDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.barcodeLocations != barcodeLocations;
}
}
class FaceDetectorPainter extends CustomPainter {
FaceDetectorPainter(this.absoluteImageSize, this.faces);
final Size absoluteImageSize;
final List<Face> faces;
#override
void paint(Canvas canvas, Size size) {
final double scaleX = size.width / absoluteImageSize.width;
final double scaleY = size.height / absoluteImageSize.height;
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..color = Colors.red;
for (Face face in faces) {
canvas.drawRect(
Rect.fromLTRB(
face.boundingBox.left * scaleX,
face.boundingBox.top * scaleY,
face.boundingBox.right * scaleX,
face.boundingBox.bottom * scaleY,
),
paint,
);
}
}
#override
bool shouldRepaint(FaceDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.faces != faces;
}
}
class LabelDetectorPainter extends CustomPainter {
LabelDetectorPainter(this.absoluteImageSize, this.labels);
final Size absoluteImageSize;
final List<Label> labels;
#override
void paint(Canvas canvas, Size size) {
final ui.ParagraphBuilder builder = ui.ParagraphBuilder(
ui.ParagraphStyle(
textAlign: TextAlign.left,
fontSize: 23.0,
textDirection: TextDirection.ltr),
);
builder.pushStyle(ui.TextStyle(color: Colors.green));
for (Label label in labels) {
builder.addText('Label: ${label.label}, '
'Confidence: ${label.confidence.toStringAsFixed(2)}\n');
}
builder.pop();
canvas.drawParagraph(
builder.build()
..layout(ui.ParagraphConstraints(
width: size.width,
)),
const Offset(0.0, 0.0),
);
}
#override
bool shouldRepaint(LabelDetectorPainter oldDelegate) {
return oldDelegate.absoluteImageSize != absoluteImageSize ||
oldDelegate.labels != labels;
}
}
You can try this:
flutter plugin: qr_code_tools
Pub Link - https://pub.dev/packages/qr_code_tools
Home Page - https://github.com/AifeiI/qr_code_tools
You can try this:
flutter plugin: scan
Pub Link - https://pub.dev/packages/scan
Home Page - https://github.com/flutter-package/flutter_scan
Follow the below steps to build a simple QR scanner and generator app in Flutter:
Step 1: First add the following dependency in your pubspec.yaml file
dependencies:
path_provider: ^1.6.24
qr_flutter: ^3.2.0
barcode_scan_fix: ^1.0.2
Main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
//Given Title
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
//Given Theme Color
theme: ThemeData(
primarySwatch: Colors.indigo,
),
//Declared first page of our app
home: HomePage(),
);
}
}
HomePage.dart
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: 500,
height: 500,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//Display Image
Image(image: NetworkImage("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQQyYwscUPOH_qPPe8Hp0HAbFNMx-TxRFubpg&usqp=CAU")),
//First Button
FlatButton(
padding: EdgeInsets.all(15),
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context)=> ScanQR()));
},
child: Text("Scan QR Code",style: TextStyle(color: Colors.indigo[900]),),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: Colors.indigo[900]),
),
),
SizedBox(height: 10),
//Second Button
FlatButton(
padding: EdgeInsets.all(15),
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context)=>
GenerateQR()));
},
child: Text("Generate QR Code", style: TextStyle(color: Colors.indigo[900]),),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: Colors.indigo[900]),
),
),
],
),
)
);
}
}
ScanQR.dart
import 'package:barcode_scan_fix/barcode_scan.dart';
import 'package:flutter/material.dart';
class ScanQR extends StatefulWidget {
#override
_ScanQRState createState() => _ScanQRState();
}
class _ScanQRState extends State<ScanQR> {
String qrCodeResult = "Not Yet Scanned";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Scan QR Code"),
),
body: Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//Message displayed over here
Text(
"Result",
style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
Text(
qrCodeResult,
style: TextStyle(
fontSize: 20.0,
),
textAlign: TextAlign.center,
),
SizedBox(
height: 20.0,
),
//Button to scan QR code
FlatButton(
padding: EdgeInsets.all(15),
onPressed: () async {
String codeSanner = await BarcodeScanner.scan(); //barcode scanner
setState(() {
qrCodeResult = codeSanner;
});
},
child: Text("Open Scanner",style: TextStyle(color: Colors.indigo[900]),),
//Button having rounded rectangle border
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.indigo[900]),
borderRadius: BorderRadius.circular(20.0),
),
),
],
),
),
);
}
}
GenerateQR.dart
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
class GenerateQR extends StatefulWidget {
#override
_GenerateQRState createState() => _GenerateQRState();
}
class _GenerateQRState extends State<GenerateQR> {
String qrData="https://github.com/ChinmayMunje";
final qrdataFeed = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
//Appbar having title
appBar: AppBar(
title: Center(child: Text("Generate QR Code")),
),
body: Container(
padding: EdgeInsets.all(20),
child: SingleChildScrollView(
//Scroll view given to Column
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
QrImage(data: qrData),
SizedBox(height: 20),
Text("Generate QR Code",style: TextStyle(fontSize: 20),),
//TextField for input link
TextField(
decoration: InputDecoration(
hintText: "Enter your link here..."
),
),
Padding(
padding: const EdgeInsets.all(8.0),
//Button for generating QR code
child: FlatButton(
onPressed: () async {
//a little validation for the textfield
if (qrdataFeed.text.isEmpty) {
setState(() {
qrData = "";
});
} else {
setState(() {
qrData = qrdataFeed.text;
});
}
},
//Title given on Button
child: Text("Generate QR Code",style: TextStyle(color: Colors.indigo[900],),),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: Colors.indigo[900]),
),
),
),
],
),
),
),
);
}
}
I want to show the animation of feedback of draggable when it is not accepted by the DropTarget.Flutter doesn't show the feedback. Is there any way we can show that or control it. Like this example, I want to achieve this effect. I somehow achieve this effect but it is not proper accurate returning to the original offset. It is moving ahead to its original position.
Animation effect I want.
Here is my Code, I have one drag box when I lift it to a certain position and leave him from there and it should animate back to original position, but it is returning to some other Offset like this.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: DragBox()),
);
}
}
class DragBox extends StatefulWidget {
DragBox({
Key key,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyDragBox();
}
}
class _MyDragBox extends State<DragBox> with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Offset begin;
Offset cancelledOffset;
Offset _offsetOfWidget;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((s) {
_afeteLayout();
});
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
}
void _afeteLayout() {
final RenderBox box = _globalKey.currentContext.findRenderObject();
Offset offset = -box.globalToLocal(Offset(0.0, 0.0));
_offsetOfWidget = offset;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: Draggable(
key: _globalKey,
onDraggableCanceled: (v, o) {
setState(() {
cancelledOffset = o;
});
_controller.forward();
},
feedback: Container(
color: Colors.red,
height: 50,
width: 50,
),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
),
),
_controller.isAnimating
? SlideTransition(
position: Tween<Offset>(
begin: cancelledOffset * 0.01,
end: _offsetOfWidget * 0.01)
.animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.stop();
} else {
_controller.reverse();
}
}),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
)
: Container(
child: Text('data'),
)
],
);
}
}
I think, this documentation about "Animate a widget using a physics simulation" is the closest example suited for what you are trying to achieve.
This recipe demonstrates how to move a widget from a dragged point
back to the center using a spring simulation.
To appreciate it in action, take a look on the example:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
final Widget child;
DraggableCard({required this.child});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}
Sample output:
A simple way to solve this problem is to create a widget that overrides Draggable and make it a child of an AnimatedPositioned. Here is the example:
import 'package:flutter/material.dart';
class XDraggable extends StatefulWidget {
const XDraggable(
{Key? key,
required this.child,
required this.original_x,
required this.original_y,
this.animation_speed = 200})
: super(key: key);
final Widget child;
final double original_x;
final double original_y;
final double animation_speed;
#override
_XDraggableState createState() => _XDraggableState();
}
class _XDraggableState extends State<XDraggable> {
double x = 200;
double y = 200;
int animation_speed = 0;
#override
void initState() {
x = widget.original_x;
y = widget.original_y;
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: x,
top: y,
duration: Duration(milliseconds: animation_speed),
child: Draggable(
onDragUpdate: (details) => {
setState(() {
animation_speed = 0;
x = x + details.delta.dx;
y = y + details.delta.dy;
}),
},
onDragEnd: (details) {
setState(() {
animation_speed = 200;
x = widget.original_x;
y = widget.original_y;
});
},
child: widget.child,
feedback: SizedBox(),
),
);
}
}
Then, just use the widget as a Stack child:
...
child: Stack(
fit: StackFit.expand,
children: [
XDraggable(
original_x: 20,
original_y: 20,
child: Container(
height: 50.0,
width: 50.0,
color: Colors.green,
),
)
],
),
...
I'm trying to animate a custom dialog box in dart so that when it pops up it create some animations. There is a library in Android that is having animated dialog boxes, is there any similar library in Flutter Sweet Alert Dialog
how can we achieve the same functionality in flutter?
To create dialog boxes you can use the Overlay or Dialog classes. If you want to add animations like in the given framework you can use the AnimationController like in the following example. The CurvedAnimation class is used to create the bouncing effect on the animation.
Update: In general it is better to show dialogs with the showDialog function, because the closing and gesture are handled by Flutter. I have updated the example, it is now running with showDialog and you are able to close the dialog by tapping on the background.
You can copy & paste the following code into a new project and adjust it. It is runnable on it's own.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', theme: ThemeData(), home: Page());
}
}
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton.icon(
onPressed: () {
showDialog(
context: context,
builder: (_) => FunkyOverlay(),
);
},
icon: Icon(Icons.message),
label: Text("PopUp!")),
),
);
}
}
class FunkyOverlay extends StatefulWidget {
#override
State<StatefulWidget> createState() => FunkyOverlayState();
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0))),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Text("Well hello there!"),
),
),
),
),
);
}
}
Just use 'showGeneralDialog()' No need use extra lib or widget.
You can get more animatated dialog reference from This Link
void _openCustomDialog() {
showGeneralDialog(barrierColor: Colors.black.withOpacity(0.5),
transitionBuilder: (context, a1, a2, widget) {
return Transform.scale(
scale: a1.value,
child: Opacity(
opacity: a1.value,
child: AlertDialog(
shape: OutlineInputBorder(
borderRadius: BorderRadius.circular(16.0)),
title: Text('Hello!!'),
content: Text('How are you?'),
),
),
);
},
transitionDuration: Duration(milliseconds: 200),
barrierDismissible: true,
barrierLabel: '',
context: context,
pageBuilder: (context, animation1, animation2) {});
}
I tried to do the animation shown in your gif. Gonna post the code to help people who want it, its not perfect so if anyone wants to help improving it go for it.
How it looks:
Code:
import 'package:flutter/material.dart';
import 'package:angles/angles.dart';
import 'dart:math';
import 'dart:core';
class CheckAnimation extends StatefulWidget {
final double size;
final VoidCallback onComplete;
CheckAnimation({this.size, this.onComplete});
#override
_CheckAnimationState createState() => _CheckAnimationState();
}
class _CheckAnimationState extends State<CheckAnimation>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> curve;
#override
void initState() {
// TODO: implement initState
super.initState();
_controller =
AnimationController(duration: Duration(seconds: 2), vsync: this);
curve = CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
_controller.addListener(() {
setState(() {});
if(_controller.status == AnimationStatus.completed && widget.onComplete != null){
widget.onComplete();
}
});
_controller.forward();
}
#override
Widget build(BuildContext context) {
return Container(
height: widget.size ?? 100,
width: widget.size ?? 100,
color: Colors.transparent,
child: CustomPaint(
painter: CheckPainter(value: curve.value),
),
);
}
#override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
class CheckPainter extends CustomPainter {
Paint _paint;
double value;
double _length;
double _offset;
double _secondOffset;
double _startingAngle;
CheckPainter({this.value}) {
_paint = Paint()
..color = Colors.greenAccent
..strokeWidth = 5.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
assert(value != null);
_length = 60;
_offset = 0;
_startingAngle = 205;
}
#override
void paint(Canvas canvas, Size size) {
// Background canvas
var rect = Offset(0, 0) & size;
_paint.color = Colors.greenAccent.withOpacity(.05);
double line1x1 = size.width / 2 +
size.width * cos(Angle.fromDegrees(_startingAngle).radians) * .5;
double line1y1 = size.height / 2 +
size.height * sin(Angle.fromDegrees(_startingAngle).radians) * .5;
double line1x2 = size.width * .45;
double line1y2 = size.height * .65;
double line2x1 =
size.width / 2 + size.width * cos(Angle.fromDegrees(320).radians) * .35;
double line2y1 = size.height / 2 +
size.height * sin(Angle.fromDegrees(320).radians) * .35;
canvas.drawArc(rect, Angle.fromDegrees(_startingAngle).radians,
Angle.fromDegrees(360).radians, false, _paint);
canvas.drawLine(Offset(line1x1, line1y1), Offset(line1x2, line1y2), _paint);
canvas.drawLine(Offset(line2x1, line2y1), Offset(line1x2, line1y2), _paint);
// animation painter
double circleValue, checkValue;
if (value < .5) {
checkValue = 0;
circleValue = value / .5;
} else {
checkValue = (value - .5) / .5;
circleValue = 1;
}
_paint.color = const Color(0xff72d0c3);
double firstAngle = _startingAngle + 360 * circleValue;
canvas.drawArc(
rect,
Angle.fromDegrees(firstAngle).radians,
Angle.fromDegrees(
getSecondAngle(firstAngle, _length, _startingAngle + 360))
.radians,
false,
_paint);
double line1Value = 0, line2Value = 0;
if (circleValue >= 1) {
if (checkValue < .5) {
line2Value = 0;
line1Value = checkValue / .5;
} else {
line2Value = (checkValue - .5) / .5;
line1Value = 1;
}
}
double auxLine1x1 = (line1x2 - line1x1) * getMin(line1Value, .8);
double auxLine1y1 =
(((auxLine1x1) - line1x1) / (line1x2 - line1x1)) * (line1y2 - line1y1) +
line1y1;
if (_offset < 60) {
auxLine1x1 = line1x1;
auxLine1y1 = line1y1;
}
double auxLine1x2 = auxLine1x1 + _offset / 2;
double auxLine1y2 =
(((auxLine1x1 + _offset / 2) - line1x1) / (line1x2 - line1x1)) *
(line1y2 - line1y1) +
line1y1;
if (checkIfPointHasCrossedLine(Offset(line1x2, line1y2),
Offset(line2x1, line2y1), Offset(auxLine1x2, auxLine1y2))) {
auxLine1x2 = line1x2;
auxLine1y2 = line1y2;
}
if (_offset > 0) {
canvas.drawLine(Offset(auxLine1x1, auxLine1y1),
Offset(auxLine1x2, auxLine1y2), _paint);
}
// SECOND LINE
double auxLine2x1 = (line2x1 - line1x2) * line2Value;
double auxLine2y1 =
((((line2x1 - line1x2) * line2Value) - line1x2) / (line2x1 - line1x2)) *
(line2y1 - line1y2) +
line1y2;
if (checkIfPointHasCrossedLine(Offset(line1x1, line1y1),
Offset(line1x2, line1y2), Offset(auxLine2x1, auxLine2y1))) {
auxLine2x1 = line1x2;
auxLine2y1 = line1y2;
}
if (line2Value > 0) {
canvas.drawLine(
Offset(auxLine2x1, auxLine2y1),
Offset(
(line2x1 - line1x2) * line2Value + _offset * .75,
((((line2x1 - line1x2) * line2Value + _offset * .75) - line1x2) /
(line2x1 - line1x2)) *
(line2y1 - line1y2) +
line1y2),
_paint);
}
}
double getMax(double x, double y) {
return (x > y) ? x : y;
}
double getMin(double x, double y) {
return (x > y) ? y : x;
}
bool checkIfPointHasCrossedLine(Offset a, Offset b, Offset point) {
return ((b.dx - a.dx) * (point.dy - a.dy) -
(b.dy - a.dy) * (point.dx - a.dx)) >
0;
}
double getSecondAngle(double angle, double plus, double max) {
if (angle + plus > max) {
_offset = angle + plus - max;
return max - angle;
} else {
_offset = 0;
return plus;
}
}
#override
bool shouldRepaint(CheckPainter old) {
return old.value != value;
}
}
I used angles package
Whenever you want to show Dialog with some Animation, the best way is to use showGeneralDialog()
NOTE: ALL PARAMETERS MUST BE PROVIDED OTHERWISE SOME ERROR WILL OCCUR.
showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5), //SHADOW EFFECT
transitionBuilder: (context, a1, a2, widget) {
return Center(
child: Container(
height: 100.0 * a1.value, // USE PROVIDED ANIMATION
width: 100.0 * a1.value,
color: Colors.blue,
),
);
},
transitionDuration: Duration(milliseconds: 200), // DURATION FOR ANIMATION
barrierDismissible: true,
barrierLabel: 'LABEL',
context: context,
pageBuilder: (context, animation1, animation2) {
return Text('PAGE BUILDER');
});
}, child: Text('Show Dialog'),),
If you need more customization, then extend PopupRoute and create your own _DialogRoute<T> and showGeneralDialog()
EDIT
Edited answer of Niklas with functionality to close Overlay :)
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', theme: ThemeData(), home: Page());
}
}
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton.icon(
onPressed: () {
OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(builder: (c) {
return FunkyOverlay(onClose: () => overlayEntry.remove());
});
Overlay.of(context).insert(overlayEntry);
},
icon: Icon(Icons.message),
label: Text("PopUp!")),
),
);
}
}
class FunkyOverlay extends StatefulWidget {
final VoidCallback onClose;
const FunkyOverlay({Key key, this.onClose}) : super(key: key);
#override
State<StatefulWidget> createState() => FunkyOverlayState();
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> opacityAnimation;
Animation<double> scaleAnimatoin;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
opacityAnimation = Tween<double>(begin: 0.0, end: 0.4).animate(
CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn));
scaleAnimatoin =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Material(
color: Colors.black.withOpacity(opacityAnimation.value),
child: Center(
child: ScaleTransition(
scale: scaleAnimatoin,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0))),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: OutlineButton(onPressed: widget.onClose, child: Text('Close!'),),
),
),
),
),
);
}
}
Add the below function in your code which shows a animated Dialog with Scale and Fade transition
void _openCustomDialog(BuildContext context) {
showGeneralDialog(
barrierColor: Colors.black.withOpacity(0.5),
transitionBuilder: (context, a1, a2, widget) {
return ScaleTransition(
scale: Tween<double>(begin: 0.5, end: 1.0).animate(a1),
child: FadeTransition(
opacity: Tween<double>(begin: 0.5, end: 1.0).animate(a1),
child: const AboutPasteLog(
title: 'About',
),
));
},
transitionDuration: const Duration(milliseconds: 300),
barrierDismissible: true,
barrierLabel: '',
context: context,
pageBuilder: (context, animation1, animation2) {
return Container();
});
}
And invoke it as below
onPressed:(){
_openCustomDialog(context);
}
The final result
Here I want to make a masking effect, like Wizard School app.
Here I am using RenderProxyBox but I can do only one mask at one time, I want to give multi-time effect. Using blendMode.clear here I am removing the cover image, and revealing reveals the image. So, is there any other way to implement multi masking effect as given in Expected section.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart: math' as math;
class DemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scratch Card',
home: Scaffold(
appBar: AppBar(
title: Text('Scratch Card'),
),
body: Material(
child: Center(
child: SizedBox(
width: 500.0,
height: 500.0,
child: Stack(
children: <Widget>[
ScratchCard(
cover: Stack(
fit: StackFit.expand,
children: <Widget>[
FittedBox(
child: Image.asset(
'assets/bird.jpg',
repeat: ImageRepeat.repeat,
),
),
],
),
reveal: DecoratedBox(
decoration: const BoxDecoration(color: Colors.black),
child: Center(
child:
FittedBox(child: Image.asset('assets/flower.jpg')),
),
),
strokeWidth: 15.0,
finishPercent: 50,
onComplete: () => print('The card is now clear!'),
),
],
),
),
),
),
),
);
}
}
class ScratchCard extends StatefulWidget {
const ScratchCard({
Key key,
this.cover,
this.reveal,
this.strokeWidth = 25.0,
this.finishPercent,
this.onComplete,
}) : super(key: key);
final Widget cover;
final Widget reveal;
final double strokeWidth;
final int finishPercent;
final VoidCallback onComplete;
#override
_ScratchCardState createState() => _ScratchCardState();
}
class _ScratchCardState extends State<ScratchCard> {
_ScratchData _data = _ScratchData();
Offset _lastPoint = null;
Offset _globalToLocal(Offset global) {
return (context.findRenderObject() as RenderBox).globalToLocal(global);
}
double _distanceBetween(Offset point1, Offset point2) {
return math.sqrt(math.pow(point2.dx - point1.dx, 2) +
math.pow(point2.dy - point1.dy, 2));
}
double _angleBetween(Offset point1, Offset point2) {
return math.atan2(point2.dx - point1.dx, point2.dy - point1.dy);
}
void _onPanDown(DragDownDetails details) {
_lastPoint = _globalToLocal(details.globalPosition);
}
void _onPanUpdate(DragUpdateDetails details) {
final currentPoint = _globalToLocal(details.globalPosition);
final distance = _distanceBetween(_lastPoint, currentPoint);
final angle = _angleBetween(_lastPoint, currentPoint);
for (double i = 0.0; i < distance; i++) {
_data.addPoint(Offset(
_lastPoint.dx + (math.sin(angle) * i),
_lastPoint.dy + (math.cos(angle) * i),
));
}
_lastPoint = currentPoint;
}
void _onPanEnd(TapUpDetails details) {
final areaRect = context.size.width * context.size.height;
double touchArea = math.pi * widget.strokeWidth * widget.strokeWidth;
double areaRevealed =
_data._points.fold(0.0, (double prev, Offset point) => touchArea);
print('areaRect $areaRect $areaRevealed');
}
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: _onPanDown,
onPanUpdate: _onPanUpdate,
onTapUp: _onPanEnd,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
widget.reveal,
_ScratchCardLayout(
strokeWidth: widget.strokeWidth,
data: _data,
child: widget.cover,
),
],
),
);
}
}
class _ScratchCardLayout extends SingleChildRenderObjectWidget {
_ScratchCardLayout({
Key key,
this.strokeWidth = 25.0,
#required this.data,
#required this.child,
}) : super(
key: key,
child: child,
);
final Widget child;
final double strokeWidth;
final _ScratchData data;
#override
RenderObject createRenderObject(BuildContext context) {
return _ScratchCardRender(
strokeWidth: strokeWidth,
data: data,
);
}
#override
void updateRenderObject(
BuildContext context, _ScratchCardRender renderObject) {
renderObject
..strokeWidth = strokeWidth
..data = data;
}
}
class _ScratchCardRender extends RenderProxyBox {
_ScratchCardRender({
RenderBox child,
double strokeWidth,
_ScratchData data,
}) : assert(data != null),
_strokeWidth = strokeWidth,
_data = data,
super(child);
double _strokeWidth;
_ScratchData _data;
set strokeWidth(double strokeWidth) {
assert(strokeWidth != null);
if (_strokeWidth == strokeWidth) {
return;
}
_strokeWidth = strokeWidth;
markNeedsPaint();
}
set data(_ScratchData data) {
assert(data != null);
if (_data == data) {
return;
}
if (attached) {
_data.removeListener(markNeedsPaint);
data.addListener(markNeedsPaint);
}
_data = data;
markNeedsPaint();
}
#override
void attach(PipelineOwner owner) {
super.attach(owner);
_data.addListener(markNeedsPaint);
}
#override
void detach() {
_data.removeListener(markNeedsPaint);
super.detach();
}
#override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.canvas.saveLayer(offset & size, Paint());
context.paintChild(child, offset);
Paint clear = Paint()..blendMode = BlendMode.clear;
_data._points.forEach((point) =>
context.canvas.drawCircle(offset + point, _strokeWidth, clear));
context.canvas.restore();
}
}
#override
bool get alwaysNeedsCompositing => child != null;
}
class _ScratchData extends ChangeNotifier {
List<Offset> _points = [];
void addPoint(Offset offset) {
_points.add(offset);
notifyListeners();
}
}
output:-
Expected: -
If I change the color/image than-
I want to change the color/image with the new one and keep previous one.
Step 1 convert JPG/PNG to ui.Image format using this code.
ui.Image uiImage;
static Future<void> cacheImage(String asset) async {
if (maskImageMap[asset] == null) {
try {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(
data.buffer.asUint8List(),
);
ui.FrameInfo fi = await codec.getNextFrame();
uiImage = fi.image;
} catch (e) {
print(e);
}
}
}
Step 2: Divide the image in the pixel format.
final Float64List deviceTransform = new Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 3.5;
Step 3: Use ImageShader to give mask filter effect.
canvas.saveLayer();
Paint().shader = ImageShader(uiImage,
TileMode.repeated, TileMode.repeated, deviceTransform);
If this answer is not enough then here is my git link
A Paint Application
I want to draw Image using the CustomPaint Class. Here I am using the RenderProxyBox to draw the image and cover with one layer, and using BlendMode.clear I am removing the layer. However I want to do that for multi image.
If anyone has any idea please comment
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart: math' as math;
class DemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scratch Card',
home: Scaffold(
appBar: AppBar(
title: Text('Scratch Card'),
),
body: Material(
child: Center(
child: SizedBox(
width: 500.0,
height: 500.0,
child: Stack(
children: <Widget>[
ScratchCard(
cover: Stack(
fit: StackFit.expand,
children: <Widget>[
FittedBox(
child: Image.asset(
'assets/bird.jpg',
repeat: ImageRepeat.repeat,
),
),
],
),
reveal: DecoratedBox(
decoration: const BoxDecoration(color: Colors.black),
child: Center(
child:
FittedBox(child: Image.asset('assets/flower.jpg')),
),
),
strokeWidth: 15.0,
finishPercent: 50,
onComplete: () => print('The card is now clear!'),
),
],
),
),
),
),
),
);
}
}
class ScratchCard extends StatefulWidget {
const ScratchCard({
Key key,
this.cover,
this.reveal,
this.strokeWidth = 25.0,
this.finishPercent,
this.onComplete,
}) : super(key: key);
final Widget cover;
final Widget reveal;
final double strokeWidth;
final int finishPercent;
final VoidCallback onComplete;
#override
_ScratchCardState createState() => _ScratchCardState();
}
class _ScratchCardState extends State<ScratchCard> {
_ScratchData _data = _ScratchData();
Offset _lastPoint = null;
Offset _globalToLocal(Offset global) {
return (context.findRenderObject() as RenderBox).globalToLocal(global);
}
double _distanceBetween(Offset point1, Offset point2) {
return math.sqrt(math.pow(point2.dx - point1.dx, 2) +
math.pow(point2.dy - point1.dy, 2));
}
double _angleBetween(Offset point1, Offset point2) {
return math.atan2(point2.dx - point1.dx, point2.dy - point1.dy);
}
void _onPanDown(DragDownDetails details) {
_lastPoint = _globalToLocal(details.globalPosition);
}
void _onPanUpdate(DragUpdateDetails details) {
final currentPoint = _globalToLocal(details.globalPosition);
final distance = _distanceBetween(_lastPoint, currentPoint);
final angle = _angleBetween(_lastPoint, currentPoint);
for (double i = 0.0; i < distance; i++) {
_data.addPoint(Offset(
_lastPoint.dx + (math.sin(angle) * i),
_lastPoint.dy + (math.cos(angle) * i),
));
}
_lastPoint = currentPoint;
}
void _onPanEnd(TapUpDetails details) {
final areaRect = context.size.width * context.size.height;
double touchArea = math.pi * widget.strokeWidth * widget.strokeWidth;
double areaRevealed =
_data._points.fold(0.0, (double prev, Offset point) => touchArea);
print('areaRect $areaRect $areaRevealed');
}
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: _onPanDown,
onPanUpdate: _onPanUpdate,
onTapUp: _onPanEnd,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
widget.reveal,
_ScratchCardLayout(
strokeWidth: widget.strokeWidth,
data: _data,
child: widget.cover,
),
],
),
);
}
}
class _ScratchCardLayout extends SingleChildRenderObjectWidget {
_ScratchCardLayout({
Key key,
this.strokeWidth = 25.0,
#required this.data,
#required this.child,
}) : super(
key: key,
child: child,
);
final Widget child;
final double strokeWidth;
final _ScratchData data;
#override
RenderObject createRenderObject(BuildContext context) {
return _ScratchCardRender(
strokeWidth: strokeWidth,
data: data,
);
}
#override
void updateRenderObject(
BuildContext context, _ScratchCardRender renderObject) {
renderObject
..strokeWidth = strokeWidth
..data = data;
}
}
class _ScratchCardRender extends RenderProxyBox {
_ScratchCardRender({
RenderBox child,
double strokeWidth,
_ScratchData data,
}) : assert(data != null),
_strokeWidth = strokeWidth,
_data = data,
super(child);
double _strokeWidth;
_ScratchData _data;
set strokeWidth(double strokeWidth) {
assert(strokeWidth != null);
if (_strokeWidth == strokeWidth) {
return;
}
_strokeWidth = strokeWidth;
markNeedsPaint();
}
set data(_ScratchData data) {
assert(data != null);
if (_data == data) {
return;
}
if (attached) {
_data.removeListener(markNeedsPaint);
data.addListener(markNeedsPaint);
}
_data = data;
markNeedsPaint();
}
#override
void attach(PipelineOwner owner) {
super.attach(owner);
_data.addListener(markNeedsPaint);
}
#override
void detach() {
_data.removeListener(markNeedsPaint);
super.detach();
}
#override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.canvas.saveLayer(offset & size, Paint());
context.paintChild(child, offset);
Paint clear = Paint()..blendMode = BlendMode.clear;
_data._points.forEach((point) =>
context.canvas.drawCircle(offset + point, _strokeWidth, clear));
context.canvas.restore();
}
}
#override
bool get alwaysNeedsCompositing => child != null;
}
class _ScratchData extends ChangeNotifier {
List<Offset> _points = [];
void addPoint(Offset offset) {
_points.add(offset);
notifyListeners();
}
}
Output:
Expected: