I am trying to create some custom widgets using CustomPaint and GestureDetector. However, I am unable to properly interact with the drawn shapes using the GestureDetector. I can detect onTap for only one shape if the container is screen width and height, or none at all.
Please note that I have tried all HitTestBehavior types.
Here is the code whilst it's not detecting anything onTap:
class CanvasObject {
Color color = Colors.green[800];
double strokeWidth = 10.0;
PaintingStyle style = PaintingStyle.stroke;
}
class CanvasNodeObject extends CanvasObject {
double x;
double y;
double radius = 20;
PaintingStyle style = PaintingStyle.fill;
CanvasNodeObject({#required this.x, #required this.y});
}
class CanvasNodePainter extends CustomPainter {
CanvasNodeObject node;
CanvasNodePainter({this.node});
#override
void paint(Canvas canvas, Size size) {
Path path;
Paint nodePaint = Paint()
..color = node.color
..style = node.style;
path = Path();
path.moveTo(0, 0);
path.addOval(
Rect.fromCircle(center: Offset(0, 0), radius: node.radius));
canvas.drawPath(path, nodePaint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
class TestLineDrawing extends StatefulWidget {
CanvasNodeObject startNode;
CanvasNodeObject endNode;
CanvasLineObject line;
TestLineDrawing({List<double> x, List<double> y})
: assert(x.length == 2),
assert(y.length == 2) {
startNode = CanvasNodeObject(x: x[0], y: y[0]);
endNode = CanvasNodeObject(x: x[1], y: y[1]);
line = CanvasLineObject(x: x, y: y);
}
#override
_TestLineDrawingState createState() => _TestLineDrawingState();
}
class _TestLineDrawingState extends State<TestLineDrawing> {
List<Widget> line() {
return [
Positioned(
top: widget.endNode.x,
left: widget.endNode.y,
child:
GestureDetector(
behavior: HitTestBehavior.opaque,
child:
Container(
height: widget.endNode.radius,
width: widget.endNode.radius,
child: CustomPaint(painter: CanvasNodePainter(node: widget.endNode))),
onTap: () {
setState(() {
Random random = Random();
widget.endNode.color = Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255));
debugPrint("EndNodeOnPress " + widget.endNode.color.toString());
});
},
)),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: line(),
));
}
On screen it is:
I resolved this issue by doing the following:
Adding all paths to a top level path called path using Path::addPath
Overriding hitTest and using Path::contains
#override
bool hitTest(Offset position) {
bool hit = path.contains(position);
return hit;
}
Related
The point where I tap creates a line from the center to that point. But currently I can't get the coordinates of the tap that match the canvas coordinates. Is there any way to change the scaffold origin (0,0) and set its origin to the same as the container origin?
If bastman hits run then I want to show the direction where he/she runs with a black line.
import 'dart:ffi';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey _cardkey = GlobalKey();
double posx = 100.0;
double posy = 100.0;
Size? cardSize;
Offset? cardPosition;
var height = AppBar().preferredSize.height;
ui.Image? image;
void onTapDown(BuildContext context, TapDownDetails details) {
// print('${details.globalPosition}');
final RenderBox box = context.findRenderObject() as RenderBox;
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
print(posx);
print(posy);
}
#override
void initState() {
// TODO: implement initState
super.initState();
// WidgetsBinding.instance!.addPostFrameCallback((_) {getSizeAndPosition(); });
loadImage('assets/images/wheel.png');
}
Future loadImage(String path) async {
final data = await rootBundle.load(path);
final bytes = data.buffer.asUint8List();
final image = await decodeImageFromList(bytes);
setState(() {
this.image = image;
});
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
// appBar: AppBar(
// title: Text("Canvas"),
// ),
body: Center(
child: Container(
decoration: BoxDecoration(
border: Border.all(),
shape: BoxShape.circle,
color: Colors.green),
height: double.infinity,
width: double.infinity,
child: GestureDetector(
key: _cardkey,
// onTapDown: (TapDownDetails details){
// onTapDown(context, details);
// },
onTap: (){},
onPanStart: (details) {
Offset position = details.localPosition;
setState(() {
posx = position.dx;
posy = position.dy;
});
print(posx);
print(posy);
},
onPanUpdate: (DragUpdateDetails details) {
Offset position = details.localPosition;
setState(() {
posx = position.dx;
posy = position.dy;
});
},
child: FittedBox(
child: SizedBox(
width: image!.width.toDouble(),
height: image!.height.toDouble(),
child: CustomPaint(
// key: _cardkey,
painter: ImagePainter(posx, posy, image),
),
),
),
),
),
),
),
);
}
}
class ImagePainter extends CustomPainter {
double? posX;
double? posY;
ui.Image? images;
ImagePainter(double posx, double posy, this.images) {
posX = posx;
posY = posy;
}
#override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final CenterX = size.width / 2;
final CenterY = size.height / 2;
final paintCircle = Paint()
..strokeWidth = 5
..color = Colors.green
..style = PaintingStyle.stroke;
// canvas.translate(size.width/2, -size.height/2);
// canvas.scale(1,-1);
final paintImage = Paint()
..strokeWidth = 5
..color = Colors.white
..style = PaintingStyle.stroke;
// canvas.translate(size.width/2, -size.height/2);
// canvas.scale(1,-1);
final paintLine = Paint()
..strokeWidth = 5
..color = Colors.black
..style = PaintingStyle.stroke;
canvas.drawCircle(center, size.width / 2, paintCircle);
var circleCenter = Offset(size.width / 2, size.height / 2);
var radius = size.width / 8;
canvas.drawImage(images!, Offset(0, 0), paintImage);
var line = canvas.drawLine(center, Offset(posX!, posY!), paintLine);
// print("$line");
}
#override
bool shouldRepaint(CustomPainter olddelegate) => true;
}
Dont use a GestureDetector. It won't give you the position of a single tap. If you want the position of a onTap you need to use a Listener:
Listener(
onPointerDown: (event) {
setState(() {
posx = event.localPosition.dx;
posy = event.localPosition.dy;
}
}
),
The localPosition property should match the coordinates of the container IF you don't resize the child of the container. If they are not correct for some reason use event.position instead which will give you coordinates in the global coordinate space.
In my flutter application I want to animate the color change (the color is a gradient) of two elements that inherit from a custom painter, this happens when a button is clicked.
My problem is that I can't find how to do it, since I understand that to animate an element a Tween is needed.
I cannot find a Tween that is correct for my case, since the color change is done in the CustomPainter conditionally.
The TweenColor class works, however I can't integrate it with a gradient and this doesn't work for me.
This effect is the one I want:
https://vimeo.com/578534813/e0f2d4ed73
This is what I have without using the Tween Color, it is fine, however, it is not the animated effect that I want.
https://vimeo.com/578538292/6e63a51060
As you can see, it is close to what is required, but it is not.
Thanks for your help, I leave the code.
ScreenClass
class AuthScreen extends StatefulWidget {
#override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> with SingleTickerProviderStateMixin
{
late AnimationController controller;
late Animation<double> opacity;
#override
void initState() {
controller = new AnimationController(vsync: this,duration: Duration(seconds:2 ));
opacity = Tween(begin: 0.1 ,end: 1.0).animate(controller);
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
controller.forward();
final authProvider = Provider.of<AuthProvider>(context,listen: false);
return Scaffold(
body: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
child: AnimatedBuilder(
animation: controller,
builder: (_,Widget? child){
return Opacity(
opacity: opacity.value,
child: CustomPaint(
painter: BackgroundPainter(provider:authProvider),
),
);
},
),
),
Center(
child: MaterialButton(
onPressed: (){
controller.reset();
controller.forward();
authProvider.flag = !authProvider.flag;
},
shape: RoundedRectangleBorder(),
elevation: 12,
color: Colors.white,
child: Text('Ingresar'),
),
),
])
);
}
}
ProviderClass
class AuthProvider extends ChangeNotifier {
bool _flag = false;
bool get flag {
print(_flag);
return this._flag;
}
set flag(bool state) => this._flag = state;
}
CustomPaintClass
class BackgroundPainter extends CustomPainter {
AuthProvider provider;
BackgroundPainter({required this.provider});
final Gradient redColor = new LinearGradient(colors: <Color>[
Colors.white12,
Color(0xffF7A2A3),
]);
final Gradient blueColor = new LinearGradient(colors: <Color>[
Colors.white12,
Color(0xff74BDD7),
]);
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromCircle(
center: Offset(55.0, 155.0),
radius: 180);
/**
* *Header*
*/
Gradient color ;
if(!provider.flag)
color = redColor;
else
color = blueColor;
final paint = new Paint()..shader = color.createShader(rect);
paint.style = PaintingStyle.fill; // .fill .stroke
paint.strokeWidth = 20;
final path = new Path();
path.moveTo(size.width, 0);
path.lineTo(size.width, size.height * 0.20);
path.quadraticBezierTo(
size.width * 0.95, size.height * 0.05, 0, size.height * 0.06);
path.lineTo(0, 0);
canvas.drawPath(path, paint);
final Rect rect2 = Rect.fromCircle(
center: Offset(55.0, 155.0), // x = horizontal y = vertical
radius: 180);
/**
* *Footer*
*/
Gradient color2 ;
if(!provider.flag)
color2 = blueColor;
else
color2 = redColor;
final paint2 = new Paint()..shader = color2.createShader(rect2);
paint2.style = PaintingStyle.fill;
paint2.strokeWidth = 30;
final path2 = new Path();
path2.moveTo(0, size.height);
path2.lineTo(0, size.height * 0.80);
path2.quadraticBezierTo(
size.width * 0.05, size.height * 0.95, size.width, size.height * 0.94);
path2.lineTo(size.width, size.height);
canvas.drawPath(path2, paint2);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Here is the code of Customized Three Containers using ClipPath for obtaining alphabet "A". This Code can also draw inside these three containers. But the drawing can be done in any direction. I want to make this code into drawing from top to bottom inside first container ,when the first drawing done then the second container is activated, after drawing of second Container then the third one to be activated (Tracing Alphabet). I think there should be 3 pairs of Draggable and DragTarget. Drawing is done by using GestureDetector. Is there is any way draw line using Draggable widget, Please respond anyone konws the solution
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIOverlays ([]);
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeRight,DeviceOrientation.landscapeLeft]).then((_){
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Offset> points1 = [];
List<Offset> points2 = [];
List<Offset> points3 = [];
#override
Widget build(BuildContext context) {
final double width = MediaQuery.of(context).size.width;
final double height = MediaQuery.of(context).size.height;
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: (){
points1.clear();
points2.clear();
points3.clear();
},
),
backgroundColor: Colors.teal,
body: SafeArea(
child:Center(
child: Stack(
children: [
Positioned(
left: MediaQuery.of(context).size.width*0.381,
top:MediaQuery.of(context).size.height*0.560,
child: ClipPath(
clipper: CPath3(),
child: Container(
height: height*0.07,
width: width*0.18,
child: GestureDetector(
onPanStart: (details){
this.setState(() {
points3.add(details.localPosition);
});
},
onPanUpdate: (details){
this.setState(() {
points3.add(details.localPosition);
});
},
onPanEnd: (details){
this.setState(() {
points3.add(null);
});
},
child: CustomPaint(
painter: MyPainter3(points3: points3),
),
),
),
),
),//THIRD PATH
Positioned(
left: MediaQuery.of(context).size.width*0.283,
// bottom: 0,
top: MediaQuery.of(context).size.height*0.194,
//right: 0,
child: ClipPath(
clipper: CPath1(),
child: Container(
height: height*0.60,
width: width*0.20,
child: GestureDetector(
onPanStart: (details){
this.setState(() {
points1.add(details.localPosition);
});
},
onPanUpdate: (details){
this.setState(() {
points1.add(details.localPosition);
});
},
onPanEnd: (details){
this.setState(() {
points1.add(null);
});
},
child: CustomPaint(
painter: MyPainter1(points1: points1),
),
),
),
),
),//FIRST PATH
Positioned(
left: MediaQuery.of(context).size.width*0.467,
// bottom: 0,
top: MediaQuery.of(context).size.height*0.194,
//right: 0,
child: ClipPath(
clipper: CPath2(),
child: Container(
height: height*0.60,
width: width*0.20,
child: GestureDetector(
onPanStart: (details){
this.setState(() {
points2.add(details.localPosition);
});
},
onPanUpdate: (details){
this.setState(() {
points2.add(details.localPosition);
});
},
onPanEnd: (details){
this.setState(() {
points2.add(null);
});
},
child: CustomPaint(
painter: MyPainter2(points2: points2),
),
),
),
),
),//SECOND PATH
],
),
),
),
);
}
}
class CPath1 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path_0 = Path();
path_0.moveTo(0,size.height);
path_0.lineTo(size.width*0.2983628,size.height);
path_0.lineTo(size.width,0);
path_0.lineTo(size.width*0.7591676,0);
path_0.lineTo(0,size.height);
path_0.close();
return path_0;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
class MyPainter1 extends CustomPainter {
List<Offset> points1;
MyPainter1({this.points1});
#override
void paint(Canvas canvas, Size size) {
Paint background = Paint()..color = Colors.white;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, background);
Paint paint = Paint();
paint.color = Colors.deepPurpleAccent;
paint.strokeWidth = 70;
paint.isAntiAlias = true;
paint.strokeCap = StrokeCap.round;
for(int x=0;x<points1.length-1;x++)
{
if(points1[x] != null && points1[x+1] != null)
{
canvas.drawLine(points1[x], points1[x+1], paint);
}
else if(points1[x] != null && points1[x+1] == null)
{
canvas.drawPoints(ui.PointMode.points,[points1[x]], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class CPath2 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path_0 = Path();
path_0.moveTo(size.width*0.6738603,size.height);
path_0.lineTo(size.width,size.height);
path_0.lineTo(size.width*0.1934179,0);
path_0.lineTo(size.width*0.08001079,0);
path_0.lineTo(0,size.height*0.1078064);
path_0.lineTo(size.width*0.6738603,size.height);
path_0.close();
return path_0;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
class MyPainter2 extends CustomPainter {
List<Offset> points2;
MyPainter2({this.points2});
#override
void paint(Canvas canvas, Size size) {
Paint background = Paint()..color = Colors.white;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, background);
Paint paint = Paint();
paint.color = Colors.deepPurpleAccent;
paint.strokeWidth = 70;
paint.isAntiAlias = true;
paint.strokeCap = StrokeCap.round;
for(int x=0;x<points2.length-1;x++)
{
if(points2[x] != null && points2[x+1] != null)
{
canvas.drawLine(points2[x], points2[x+1], paint);
}
else if(points2[x] != null && points2[x+1] == null)
{
canvas.drawPoints(ui.PointMode.points,[points2[x]], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class CPath3 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path_0 = Path();
path_0.moveTo(size.width*0.9108546,0);
path_0.lineTo(size.width*0.08746101,0);
path_0.lineTo(0,size.height);
path_0.lineTo(size.width,size.height*0.9771505);
path_0.lineTo(size.width*0.9108546,0);
path_0.close();
return path_0;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
class MyPainter3 extends CustomPainter {
List<Offset> points3;
MyPainter3({this.points3});
#override
void paint(Canvas canvas, Size size) {
Paint background = Paint()..color = Colors.white;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRect(rect, background);
Paint paint = Paint();
paint.color = Colors.deepPurpleAccent;
paint.strokeWidth = 70;
paint.isAntiAlias = true;
paint.strokeCap = StrokeCap.round;
for(int x=0;x<points3.length-1;x++)
{
if(points3[x] != null && points3[x+1] != null)
{
canvas.drawLine(points3[x], points3[x+1], paint);
}
else if(points3[x] != null && points3[x+1] == null)
{
canvas.drawPoints(ui.PointMode.points,[points3[x]], paint);
}
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
I am beginner in Flutter and I am trying to figure how I can detect touch enter, move and exit when a user runs their finger across a custom shape and/or across multiple stacked custom shapes. Something like below
Ideally I would like to get touch events when users enter/exit pixel boundaries of each custom shape, but I wanted to get it working at-least with a MBR of the shapes. Below is the code that I have. What am I doing wrong? All it seems to do is print move when touch begins within the shapes. I have tried GestureDetector too with similar results.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
),
home: MyHomePage(title: 'Flutter Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Stack(
children: <Widget>[
Listener(
onPointerSignal: (PointerEvent details) {
print("Signal yellow");
},
onPointerMove: (PointerEvent details) {
print("Move yellow");
},
onPointerHover: (PointerEvent details) {
print("Hover yellow");
},
onPointerEnter: (PointerEvent details) {
print("Enter yellow");
},
onPointerExit: (PointerEvent details) {
print("Exit yellow");
},
child: CustomPaint(
painter: ShapesPainter(),
child: Container(
height: 400,
width: 400,
),
),
),
Listener(
onPointerEnter: (PointerEvent details) {
print("Enter red");
},
onPointerExit: (PointerEvent details) {
print("Exit red");
},
child: CustomPaint(
painter: ShapesPainter1(),
child: Container(
height: 200,
width: 200,
),
),
),
],
),
],
),
),
);
}
}
class ShapesPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
// set the color property of the paint
paint.color = Colors.yellow;
// center of the canvas is (x,y) => (width/2, height/2)
var center = Offset(size.width / 2, size.height / 2);
// draw the circle on centre of canvas having radius 75.0
canvas.drawCircle(center, size.width / 2, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
#override
bool hitTest(Offset position) {
// TODO: implement hitTest
return super.hitTest(position);
}
}
class ShapesPainter1 extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
// set the color property of the paint
paint.color = Colors.red;
// center of the canvas is (x,y) => (width/2, height/2)
var center = Offset(size.width / 2, size.height / 2);
// draw the circle on centre of canvas having radius 75.0
canvas.drawCircle(center, size.width / 2, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
#override
bool hitTest(Offset position) {
// TODO: implement hitTest
return super.hitTest(position);
}
}
That's because you are using one Listener per CustomPainter, you should use just one Listener for all your Stack.
And if you want to know if the current touch event is inside each Circle , you could use GlobalKeys to get the RenderBox for each Circle, then you have the renderBox, and the PointerEvent, you can easily check the HitTest, check the code:
class _MyHomePageState extends State<MyHomePage> {
GlobalKey _keyYellow = GlobalKey();
GlobalKey _keyRed = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Listener(
onPointerMove: (PointerEvent details) {
final RenderBox box = _keyRed.currentContext.findRenderObject();
final RenderBox boxYellow =
_keyYellow.currentContext.findRenderObject();
final result = BoxHitTestResult();
Offset localRed = box.globalToLocal(details.position);
Offset localYellow = boxYellow.globalToLocal(details.position);
if (box.hitTest(result, position: localRed)) {
print("HIT...RED ");
} else if (boxYellow.hitTest(result, position: localYellow)) {
print("HIT...YELLOW ");
}
},
child: Stack(
children: <Widget>[
CustomPaint(
key: _keyYellow,
painter: ShapesPainter(),
child: Container(
height: 400,
width: 400,
),
),
CustomPaint(
key: _keyRed,
painter: ShapesPainter1(),
child: Container(
height: 200,
width: 200,
),
),
],
),
),
],
),
),
);
}
}
Also I modified the hitTest method of your CustomPainters to ignore the touchs outside the circle.
class ShapesPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
// set the color property of the paint
paint.color = Colors.yellow;
// center of the canvas is (x,y) => (width/2, height/2)
final center = Offset(size.width / 2, size.height / 2);
// draw the circle on centre of canvas having radius 75.0
canvas.drawCircle(center, size.width / 2, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
#override
bool hitTest(Offset position) {
final Offset center = Offset(200, 200);
Path path = Path();
path.addRRect(RRect.fromRectAndRadius(
Rect.fromCenter(center: center, width: 400, height: 400),
Radius.circular(center.dx)));
path.close();
return path.contains(position);
}
}
class ShapesPainter1 extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
// set the color property of the paint
paint.color = Colors.red;
// center of the canvas is (x,y) => (width/2, height/2)
var center = Offset(size.width / 2, size.height / 2);
// draw the circle on centre of canvas having radius 75.0
canvas.drawCircle(center, size.width / 2, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
#override
bool hitTest(Offset position) {
final Offset center = Offset(100, 100);
Path path = Path();
path.addRRect(RRect.fromRectAndRadius(
Rect.fromCenter(center: center, width: 200, height: 200),
Radius.circular(center.dx)));
path.close();
return path.contains(position);
}
}
I have developed a library called
touchable for the purpose of adding gesture callbacks to each individual shape you draw on the canvas.
Here's what you can do to detect touch and drag on your circle.
Just Wrap your CustomPaint widget with CanvasTouchDetector. It takes a builder function as argument that expects your CustomPaint widget as shown below.
import 'package:touchable/touchable.dart';
CanvasTouchDetector(
builder: (context) =>
CustomPaint(
painter: MyPainter(context)
)
)
Inside your CustomPainter class's paint method , create and use the TouchyCanvas object (using the context obtained from the CanvasTouchDetector and canvas) to draw your shape and you can give gesture callbacks like onPanUpdate , onTapDown here to detect your drag events.
var myCanvas = TouchyCanvas(context,canvas);
myCanvas.drawRect( rect , Paint() , onPanUpdate: (detail){
//This callback runs when you drag this rectangle. Details of the location can be got from the detail object.
//Do stuff here. Probably change your state and animate
});
i am trying to achieved some thing like this in flutter
One way is with CustomPainter and an animation. Also look at SpriteWidget.
import 'dart:math';
import 'package:flutter/material.dart';
class SpritePainter extends CustomPainter {
final Animation<double> _animation;
SpritePainter(this._animation) : super(repaint: _animation);
void circle(Canvas canvas, Rect rect, double value) {
double opacity = (1.0 - (value / 4.0)).clamp(0.0, 1.0);
Color color = Color.fromRGBO(0, 117, 194, opacity);
double size = rect.width / 2;
double area = size * size;
double radius = sqrt(area * value / 4);
final Paint paint = Paint()..color = color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(SpritePainter oldDelegate) {
return true;
}
}
class SpriteDemo extends StatefulWidget {
#override
SpriteDemoState createState() => SpriteDemoState();
}
class SpriteDemoState extends State<SpriteDemo>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
);
//_startAnimation();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startAnimation() {
_controller
..stop()
..reset()
..repeat(period: const Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pulse')),
body: CustomPaint(
painter: SpritePainter(_controller),
child: SizedBox(
width: 200.0,
height: 200.0,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _startAnimation,
child: new Icon(Icons.play_arrow),
),
);
}
}
void main() {
runApp(
MaterialApp(
home: SpriteDemo(),
),
);
}
You can also use Flare with: flare_flutter. It's more simple.
For those looking to create an animation which is not circular, but rectangular with possible rounded borders, you can replace the SpritePainter from the top answer with:
class SpritePainter extends CustomPainter {
final Animation<double> _animation;
SpritePainter(this._animation) : super(repaint: _animation);
void roundedRect(Canvas canvas, Rect rect, double animValue, int waveAmount) {
double opacity = (1.0 - (animValue / waveAmount)).clamp(0.0, 1.0);
Color color = new Color.fromRGBO(0, 117, 194, opacity);
final pixelMiltiplier = 20;
final newWidth = rect.width + animValue*pixelMiltiplier;
final newHeight = rect.height + animValue*pixelMiltiplier;
final widthIncrease = newWidth/rect.width;
final heightIncrease = newHeight/rect.height;
final widthOffset = (widthIncrease - 1) / 2;
final heightOffet = (heightIncrease - 1) / 2;
final Paint paint = new Paint()..color = color;
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(-rect.width * widthOffset, -rect.height * heightOffet,
rect.width * widthIncrease, rect.height * heightIncrease),
Radius.circular(15.0)),
paint);
}
#override
void paint(Canvas canvas, Size size) {
Rect rect = new Rect.fromLTRB(0.0, 0.0, size.width, size.height);
final waveAmount = 1;
if (!_animation.isDismissed) {
for (int wave = waveAmount-1; wave >= 0; wave--) {
roundedRect(canvas, rect, wave + _animation.value, waveAmount);
}
}
}
#override
bool shouldRepaint(SpritePainter oldDelegate) {
return true;
}
}