How to draw custom shape in flutter and drag that shape around? - flutter

At the moment I can draw rectangles using CustomPainter. Below the code inside the paint method of my CustomPainter.
for (var rectPoints in rectangles) {
paint.color = rectPoints.color;
paint.strokeWidth = rectPoints.strokeWidth;
if (rectPoints.selected != null && rectPoints.selected == true) {
paint.color = Colors.black45;
}
var rect = Rect.fromLTWH(
rectPoints.startPoint.dx,
rectPoints.startPoint.dy,
rectPoints.endPoint.dx - rectPoints.startPoint.dx,
rectPoints.endPoint.dy - rectPoints.startPoint.dy);
canvas.drawRect(rect, paint);
}
var rect = Rect.fromLTWH(startPoint.dx, startPoint.dy,
endPoint.dx - startPoint.dx, endPoint.dy - startPoint.dy);
canvas.drawRect(rect, paint);
A rectangle is a custom object with startPoint, endPoint and some other properties needed to draw that specific rectangle. Now I want to select a rectangle and re-position it. Any help would be appreciated. Thanks

You'll need to track the state of the rectangles' positions independent of the canvas drawing. The easiest way to do that is to use a StatefulWidget. You'll also need to use a GestureDetector to capture the pan events. Then you can wire up the gesture details to the position of the rectangles and call the painter to redraw everything.
Here's a simple app that shows how to do it with one rectangle. Should be straightforward to expand it to handle multiple ones.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Custom Painter',
home: Scaffold(
body: CustomPainterDraggable(),
),
);
}
}
class CustomPainterDraggable extends StatefulWidget {
#override
_CustomPainterDraggableState createState() => _CustomPainterDraggableState();
}
class _CustomPainterDraggableState extends State<CustomPainterDraggable> {
var xPos = 0.0;
var yPos = 0.0;
final width = 100.0;
final height = 100.0;
bool _dragging = false;
/// Is the point (x, y) inside the rect?
bool _insideRect(double x, double y) =>
x >= xPos && x <= xPos + width && y >= yPos && y <= yPos + height;
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (details) => _dragging = _insideRect(
details.globalPosition.dx,
details.globalPosition.dy,
),
onPanEnd: (details) {
_dragging = false;
},
onPanUpdate: (details) {
if (_dragging) {
setState(() {
xPos += details.delta.dx;
yPos += details.delta.dy;
});
}
},
child: Container(
color: Colors.white,
child: CustomPaint(
painter: RectanglePainter(Rect.fromLTWH(xPos, yPos, width, height)),
child: Container(),
),
),
);
}
}
class RectanglePainter extends CustomPainter {
RectanglePainter(this.rect);
final Rect rect;
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(rect, Paint());
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

I have developed a library called
touchable for the purpose of adding gesture callbacks to each individual shape you draw on the canvas. You can draw your shapes and add onPanUpdate or onTapDown callbacks to drag your shape around.
Here's what you can do to detect touch and drag on your circle.
Here's a small example taken directly from the pub dev site :
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
});

Related

Flutter Custom Shape

I need to make a shape as seen in the photo below with container in Flutter, can you help me for this?
use this code to create shape like this
import 'dart:ui' as ui;
//Add this CustomPaint widget to the Widget Tree
CustomPaint(
size: Size(WIDTH, (WIDTH*0.3542581280172481).toDouble()), //You can Replace [WIDTH] with your desired width for Custom Paint and height will be calculated automatically
painter: RPSCustomPainter(),
)
//Copy this CustomPainter code to the Bottom of the File
class RPSCustomPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Path path_0 = Path();
path_0.moveTo(size.width*1.156952,size.height*3.936736);
path_0.lineTo(size.width*1.968605,size.height*3.949394);
path_0.cubicTo(size.width*1.968605,size.height*3.949394,size.width*2.151398,size.height*4.204300,size.width*2.152460,size.height*4.443043);
path_0.cubicTo(size.width*2.153491,size.height*4.675744,size.width*1.977573,size.height*4.936736,size.width*1.977573,size.height*4.936736);
path_0.lineTo(size.width*1.152468,size.height*4.898765);
path_0.lineTo(size.width*1.156952,size.height*3.936736);
path_0.close();
Paint paint_0_fill = Paint()..style=PaintingStyle.fill;
paint_0_fill.color = Color.fromRGBO(216, 216, 216,1.0).withOpacity(1.0);
canvas.drawPath(path_0,paint_0_fill);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

How draw a rectangle directly on an image in flutter

I want to draw a rectangle on the actual image and don't want to use the flutter renderer to add a rectangle on the top layer just as a view. I can get the pixel color, but how can I overwrite the bonch of pixels?
ByteData byteData = await rootBundle.load('assets/images/maps/map.jpg');
Uint8List bytes = byteData.buffer.asUint8List();
List<int> values = bytes;
img.Image photo = img.decodeImage(values)!;
final pixels = photo.getBytes(format: img.Format.rgba);
I´m not quite sure, if this is the answer you need, but what about using a Stack?
Stack(children: [
image,
Positioned.fill(
child: CustomPaint(
painter: Sky(r),
child: Container()));
}),
],
)
...
class Sky extends CustomPainter {
Rect rect;
Sky(this.rect);
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(rect, Paint()..color = Colors.black);
}
#override
bool shouldRepaint(Sky oldDelegate) => false;
}

How to detect device rotation (orientation change) in flutter?

My app has a draggable floating action button that could move everywhere on the screen and stay at that position when being dropped.
The code for the button is from a blogspot and not mine.
The problem is, since everything is set in portrait mode, and there is no detection when rotation occurs, the button could be unreachable when device rotates from portrait mode to landscape mode (if the button was previously on the bottom half of the screen in portrait mode).
Is there anyway to detect previous orientation in order to set the offset for the button again when it rotates?
Here is the code for the floating button class/widget
import 'package:flutter/material.dart';
class DraggableFloatingActionButton extends StatefulWidget {
final Widget child;
final double deviceWidth;
final double deviceHeight;
final Function onPressed;
final GlobalKey parentKey;
DraggableFloatingActionButton({
required this.child,
required this.deviceWidth,
required this.deviceHeight,
required this.onPressed,
required this.parentKey,
});
#override
State<StatefulWidget> createState() => _DraggableFloatingActionButtonState();
}
class _DraggableFloatingActionButtonState extends State<DraggableFloatingActionButton> {
final GlobalKey _key = GlobalKey();
bool _isDragging = false;
late Offset _offset;
late Offset _minOffset;
late Offset _maxOffset;
#override
void initState() {
_offset = Offset(widget.deviceWidth * 0.8, widget.deviceHeight * 0.75);
WidgetsBinding.instance.addPostFrameCallback(_setBoundary);
super.initState();
}
void _setBoundary(_) {
final RenderBox parentRenderBox =
widget.parentKey.currentContext?.findRenderObject() as RenderBox;
final RenderBox renderBox = _key.currentContext?.findRenderObject() as RenderBox;
try {
final Size parentSize = parentRenderBox.size;
final Size size = renderBox.size;
setState(() {
_minOffset = const Offset(0, 0);
_maxOffset = Offset(parentSize.width - size.width, parentSize.height - size.height);
});
} catch (e) {
print('catch: $e');
}
}
void _updatePosition(PointerMoveEvent pointerMoveEvent) {
double newOffsetX = _offset.dx + pointerMoveEvent.delta.dx;
double newOffsetY = _offset.dy + pointerMoveEvent.delta.dy;
if (newOffsetX < _minOffset.dx) {
newOffsetX = _minOffset.dx;
} else if (newOffsetX > _maxOffset.dx) {
newOffsetX = _maxOffset.dx;
}
if (newOffsetY < _minOffset.dy) {
newOffsetY = _minOffset.dy;
} else if (newOffsetY > _maxOffset.dy) {
newOffsetY = _maxOffset.dy;
}
setState(() {
_offset = Offset(newOffsetX, newOffsetY);
});
}
#override
Widget build(BuildContext context) {
return Positioned(
left: _offset.dx,
top: _offset.dy,
child: Listener(
onPointerMove: (PointerMoveEvent pointerMoveEvent) {
_updatePosition(pointerMoveEvent);
setState(() {
_isDragging = true;
});
},
onPointerUp: (PointerUpEvent pointerUpEvent) {
print('onPointerUp');
if (_isDragging) {
setState(() {
_isDragging = false;
});
} else {
widget.onPressed();
}
},
child: Container(
key: _key,
child: widget.child,
),
),
);
}
}
As you can see, it only sets the button's offset based on portrait mode once in initState function, and doesn't deal with rotation.
A walk around that I could think of at this moment is just having another floating button specifically set for landscape mode.
Thank you in advance for any answer.
Ok, so I still don't know how to detect rotation, but my walk around works.
All I do is create another class(widget) for a landscape floating button.
In main, check device orientation to use either the floating button portrait class or landscape class.
The only thing that I change in the landscape button class is
from
_offset = Offset(widget.deviceWidth * 0.8, widget.deviceHeight * 0.75);
to
_offset = Offset(widget.deviceHeight * 1.75, widget.deviceWidth * 0.3);
You shouldn't care about orientation. You should use something like LayoutBuilder to determine your space available, and make breakpoint decisions based on that. LayoutBuilder will update immediately if the device space changes, and you should react to that by doing another layout.

Flutter) I want to know how to detect the number of fingers with the GestureDetector

I'm making a whiteboard app and I'm going to implement the draw and zoom functions through the GestureDetector.
Each test went well, but if I use both together, only Zoom function is used in onScaleUpdate() and Draw function is not output.
So I'm going to implement it so that if I touch two fingers, I can only use the Zoom function, and if I touch one, I can only use the Draw function.
Can you tell the number of fingers touched using the Gesture Detector?
Or is there another good way?
The following is part of my code
GestureDetector(
onScaleStart: (details) {
Tool tool = context.read<DrawProvider>().tool;
double seletedPenWidth = context.read<DrawProvider>().seletedPenWidth;
Color seletedPenColor = context.read<DrawProvider>().seletedPenColor;
RenderBox box = context.findRenderObject() as RenderBox;
// zoom test
context.read<DrawProvider>().onScaleStart(details);
// Use Pen
if (tool == Tool.pen) {
Offset point = box.globalToLocal(details.focalPoint);
point = Offset(point.dx, point.dy);
currentLine = DrawingModel(
pointList: [point],
color: seletedPenColor,
width: seletedPenWidth,
);
} else {
// TODO Other Tool
}
},
onScaleUpdate: (details) {
Tool tool = context.read<DrawProvider>().tool;
double seletedPenWidth = context.read<DrawProvider>().seletedPenWidth;
Color seletedPenColor = context.read<DrawProvider>().seletedPenColor;
RenderBox box = context.findRenderObject() as RenderBox;
// zoom test
context.read<DrawProvider>().onScaleUpdate(details);
if (tool == Tool.pen) {
Offset point =
box.globalToLocal(details.focalPoint);
point = Offset(point.dx, point.dy);
List<Offset> path = List.from(currentLine!.pointList!)..add(point);
currentLine = DrawingModel(
pointList: path,
color: seletedPenColor,
width: seletedPenWidth,
);
currentLineStreamController.add(currentLine!);
}
},
onScaleEnd: (details) {
Tool tool = context.read<DrawProvider>().tool;
// zoom test
context.read<DrawProvider>().onScaleEnd(details);
if (tool == Tool.pen) {
allLines = List.from(allLines)..add(currentLine!);
linesStreamController.add(allLines);
}
}
provider.dart, zoom functions
Offset _offset = Offset.zero;
Offset _initialFocalPoint = Offset.zero;
Offset _sessionOffset = Offset.zero;
double _scale = 1.0;
double _initialScale = 1.0;
void onScaleStart(ScaleStartDetails details) {
// TODO if use move tool
// _initialFocalPoint = details.focalPoint;
_initialScale = _scale;
notifyListeners();
}
void onScaleUpdate(ScaleUpdateDetails details) {
// TODO if use move tool
// _sessionOffset = details.focalPoint - _initialFocalPoint;
_scale = _initialScale * details.scale;
notifyListeners();
}
void onScaleEnd(ScaleEndDetails details) {
// TODO if use move tool
// _offset += _sessionOffset;
_sessionOffset = Offset.zero;
notifyListeners();
}
whiteboard screen widget
Transform.translate(
offset: _offset + _sessionOffset,
child: Transform.scale(
scale: _scale,
child: buildAllPaths(allLines: allLines), // drawing screen
),
),
Use details.pointerCount from onScaleUpdate and onScaleStart in GestureDetector.

Flutter Create circle with multiple sections with touch gesture

I want to create widget using custom painter like following with touch gesture;
Draw svg of this image. Then convert into flutter paint code using
https://fluttershapemaker.com/
Add the touchable package as dependency in your pubspec.yaml
https://github.com/nateshmbhat/touchable
Example :
CanvasTouchDetector(
builder: (context) =>
CustomPaint(
painter: MyPainter(context)
)
)
class MyPainter extends CustomPainter {
final BuildContext context ;
MyPainter(this.context); // context from CanvasTouchDetector
#override
void paint(Canvas canvas, Size size) {
var myCanvas = TouchyCanvas(context,canvas);
myCanvas.drawCircle(Offset(10, 10), 60, Paint()..color=Colors.orange ,
onTapDown: (tapdetail) {
print("orange Circle touched");
},
onPanDown:(tapdetail){
print("orange circle swiped");
}
);
myCanvas.drawLine(
Offset(0, 0),
Offset(size.width - 100, size.height - 100),
Paint()
..color = Colors.black
..strokeWidth = 50,
onPanUpdate: (detail) {
print('Black line Swiped'); //do cooler things here. Probably change app state or animate
});
}
}