Draw custom Arc shape flutter - flutter

I'm trying to draw this kind of shape with flutter:
Expected result
So far I can draw an arc using drawArc():
class CurvePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.green;
paint.style = PaintingStyle.fill;
paint.strokeWidth = 5;
final rect = Rect.fromLTRB(50, 100, 130, 200);
final startAngle = -pi;
final sweepAngle = pi;
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
But is that the right way to do it or should I use quadraticBezierTo and drawPath?

Related

Flutter How to create a Curved Slider?

This is an image I found on Stackoverflow only and I want a Slider shaped like this. I tried packages like sleek_circular_slider and other packages but I was not able to create this shape.
I can create this shape in CustomPainter and when I tried to use SliderTrackShape to customize the SLider Shape it didn't worked.
class CustomSlider extends SliderTrackShape {
#override
Rect getPreferredRect({
RenderBox? parentBox,
Offset offset = Offset.zero,
SliderThemeData? sliderTheme,
bool? isEnabled,
bool? isDiscrete,
}) {
final double thumbWidth =
sliderTheme!.thumbShape!.getPreferredSize(true, isDiscrete!).width;
final double? trackHeight = sliderTheme.trackHeight;
assert(thumbWidth >= 0);
assert(trackHeight! >= 0);
assert(parentBox!.size.width >= thumbWidth);
assert(parentBox!.size.height >= trackHeight!);
final double trackLeft = offset.dx + thumbWidth / 2;
final double trackTop =
offset.dy + (parentBox!.size.height - trackHeight!) / 2;
final double trackWidth = parentBox.size.width - thumbWidth;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
#override
void paint(
PaintingContext context,
Offset offset, {
RenderBox? parentBox,
SliderThemeData? sliderTheme,
Animation<double>? enableAnimation,
TextDirection? textDirection,
Offset? thumbCenter,
bool? isDiscrete,
bool? isEnabled,
}) {
if (sliderTheme!.trackHeight == 0) {
return;
}
final Rect trackRect = getPreferredRect(
parentBox: parentBox,
offset: offset,
sliderTheme: sliderTheme,
isEnabled: isEnabled,
isDiscrete: isDiscrete,
);
final Paint fillPaint = Paint()
..color = sliderTheme.activeTrackColor!
..style = PaintingStyle.fill;
final Paint borderPaint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
final pathSegment = Path()
..moveTo(0, 2500)
..quadraticBezierTo(250 / 2, 250, 250, 250 / 2);
context.canvas.drawPath(pathSegment, fillPaint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
This code renders just the thumb shape and no slider when I use this widget inside tracakShape for SliderTheme. But when I use similar code for CustomShape I am able to create this shape successfully like this :
class CustomSlider extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Color(0xff84CADE);
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 4.0;
Path path = Path();
path.moveTo(0, size.height / 2);
path.quadraticBezierTo(size.width / 2, size.height, size.width, size.height / 2);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
So I guess my question is how can I render this shape that I am succesfully rendering when I am extending from SliderTrackShape as I am unable to do so when trying to extend from SliderTrackShape class and create this shape.

Flutter paintZigZag

Help me in drawing the zigZag line/border as show in the image at bottom. I found a zigzag paint function in flutter doc https://api.flutter.dev/flutter/painting/paintZigZag.html but not sure how to use it.
You need to use CustomPaint() Widget.
Create your own CustomerPainter, say MyPainter, and place this in your widget tree:
CustomPaint(
size: MediaQuery.of(context).size,
painter: MyPainter(),
),
The class MyPainter should extend CustomPainter and also should override the paint() method which is where you specify the path to be painted on the canvas.
So you can just put the above 'paintZigZag' code there or call it like below along with appropriate parameters.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.blue;
paint.style = PaintingStyle.fill;
paintZigZag(canvas, paint, Offset(0, 100), Offset(200, 100), 100, 5);
}
void paintZigZag(Canvas canvas, Paint paint, Offset start, Offset end,
int zigs, double width) {
assert(zigs.isFinite);
assert(zigs > 0);
canvas.save();
canvas.translate(start.dx, start.dy);
end = end - start;
canvas.rotate(math.atan2(end.dy, end.dx));
final double length = end.distance;
final double spacing = length / (zigs * 2.0);
final Path path = Path()..moveTo(0.0, 0.0);
for (int index = 0; index < zigs; index += 1) {
final double x = (index * 2.0 + 1.0) * spacing;
final double y = width * ((index % 2.0) * 2.0 - 1.0);
path.lineTo(x, y);
}
path.lineTo(length, 0.0);
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

CustomPainter and CustomClipper produce different outputs for same path

I'm using path drawing. I must scale that path. I'm using bounds for scale but It doesn't work same for clipper and painter. I need same output.
Here is codes.
class PaintSvg extends CustomPainter{
PaintSvg(this.pathStr);
String pathStr;
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
paint.style = PaintingStyle.fill;
paint.color = Colors.red;
Path path = Path();
path = parseSvgPathData(pathStr);
Rect pathBounds = path.getBounds();
Matrix4 matrix4 = Matrix4.identity();
matrix4.scale(size.width/pathBounds.width, size.height/pathBounds.height);
path = path.shift(Offset(0,0));
path = path.transform(matrix4.storage);
canvas.drawShadow(path, Colors.green, 2, true);
//canvas.rotate(-math.pi/1.18);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
class ClipSvg extends CustomClipper<Path>{
String pathStr;
ClipSvg(this.pathStr);
#override
Path getClip(Size size) {
Path path = Path();
path = parseSvgPathData(pathStr);
Rect pathBounds = path.getBounds();
Matrix4 matrix4 = Matrix4.identity();
matrix4.scale(size.width/pathBounds.width, size.height/pathBounds.height);
path = path.transform(matrix4.storage);
path = path.shift(Offset(0,0));
return path;
}
#override
bool shouldReclip(ClipSvg oldClipper) => true;
}
Here is output.
ScreenShot of output
Brown one is clipper red one that behind the brown is painter. Two of them uses same svg data string.

Flutter Improve CustomPainter animation performance

I needed a loading widget that draws the moving sine and cosine functions into a canvas. I coded it with no problem using a CustomPaint widget and a CustomPainter, but when I profile it, i Have discovered it runs on about 49fps, and not on 60fps. The UI thread is working good, taking about 6ms for each frame, but the Raster thread is taking longer. I have tried painting less points on the canvas (doing i=i+5 instead of i++ on the for loop), but the result is quite the same.
¿Can somebody suggest me an idea on how could I improve the performance?. The widget code is below, and so is the DevTools screenshot of what the Raster thread is doing in every frame, in case it can be useful.
import 'dart:math';
import 'package:flutter/material.dart';
class LoadingChart extends StatefulWidget{
final Color color1;
final Color color2;
final double lineWidth;
final bool line;
final Size size;
const LoadingChart({
#required this.color1,
#required this.color2,
#required this.size,
#required this.lineWidth,
this.line = true,
Key key
}): super(key: key);
#override
State<StatefulWidget> createState() => _LoadingChartState();
}
class _LoadingChartState extends State<LoadingChart>
with SingleTickerProviderStateMixin{
AnimationController _controller;
double randomHeight(Random random, double max){
return random.nextDouble()*max;
}
#override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
_controller.addListener(() {setState(() {});});
_controller.repeat();
super.initState();
}
#override
void dispose(){
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: CustomPaint(
painter: PathPainter(
color1: widget.color1,
color2: widget.color2,
value: _controller.value,
line: widget.line,
),
)
);
}
}
class PathPainter extends CustomPainter {
final Color color1;
final Color color2;
final double lineWidth;
final bool line;
final double value;
PathPainter({
#required this.value,
this.color1=Colors.red,
this.color2=Colors.green,
this.line = true,
this.lineWidth=4.0,
}): super();
#override
void paint(Canvas canvas, Size size) {
final height = size.height;
final width = size.width;
Paint paint1 = Paint()
..color = color1
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Paint paint2 = Paint()
..color = color2
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Path path1 = Path();
Path path2 = Path();
/* If line is true, draw sin and cos functions, otherwise, just some points */
for (double i = 0; i < width; i=i+5){
double f = i*2*pi/width + 2*pi*value;
double g = i*2*pi/width - 2*pi*value;
if (i == 0){
path1.moveTo(0, height/2 + height/6*sin(f));
path2.moveTo(0, height/2 + height/6*cos(g));
continue;
}
path1.lineTo(i, height/2 + height/6*sin(f));
path2.lineTo(i, height/2 + height/6*cos(g));
}
/* Draw both lines */
canvas.drawPath(path1, paint1);
canvas.drawPath(path2, paint2);
}
#override
bool shouldRepaint(PathPainter oldDelegate) {
return oldDelegate.value != value || oldDelegate.color1 != color1
|| oldDelegate.color2 != color2 || oldDelegate.line != line
|| oldDelegate.lineWidth != lineWidth;
}
}
PS: I'm running the app on profile mode so that shouldn't be the problem. Also I wanted to mention that it's the only widget being redrawn on the screen.
Thanks a lot!!
CustomPainter can receive a listenable so maybe you can use the animation controller there to update it with every tick
class _LoadingChartState extends State<LoadingChart>
with SingleTickerProviderStateMixin{
AnimationController _controller;
double randomHeight(Random random, double max){
return random.nextDouble()*max;
}
#override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
//_controller.addListener(() {setState(() {});}); no need to setState
_controller.repeat();
super.initState();
}
#override
void dispose(){
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: CustomPaint(
willChange: true, //this can help (Whether the raster cache should be told that this painting is likely)
painter: PathPainter(
color1: widget.color1,
color2: widget.color2,
line: widget.line,
listenable: _controller //pass the controller as it is (An animationController extends a Listenable)
),
)
);
}
}
And in PathPainter you give to the constructor the listenable and pass it to the CustomPainter constructor that accepts a listenable called repaint
class PathPainter extends CustomPainter {
final Animation listenable;
final Color color1;
final Color color2;
final double lineWidth;
final bool line;
PathPainter({
this.listenable,
this.color1=Colors.red,
this.color2=Colors.green,
this.line = true,
this.lineWidth=4.0,
}): super(repaint: listenable); //don't forget calling the CustomPainter constructor with super
#override
void paint(Canvas canvas, Size size) {
double value = listenable.value; // get its value here
final height = size.height;
final width = size.width;
Paint paint1 = Paint()
..color = color1
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Paint paint2 = Paint()
..color = color2
..style = PaintingStyle.stroke
..strokeWidth = lineWidth;
Path path1 = Path();
Path path2 = Path();
/* If line is true, draw sin and cos functions, otherwise, just some points */
for (double i = 0; i < width; i=i+5){
double f = i*2*pi/width + 2*pi*value;
double g = i*2*pi/width - 2*pi*value;
if (i == 0){
path1.moveTo(0, height/2 + height/6*sin(f));
path2.moveTo(0, height/2 + height/6*cos(g));
continue;
}
path1.lineTo(i, height/2 + height/6*sin(f));
path2.lineTo(i, height/2 + height/6*cos(g));
}
/* Draw both lines */
canvas.drawPath(path1, paint1);
canvas.drawPath(path2, paint2);
}
#override
bool shouldRepaint(PathPainter oldDelegate) {
//delete the oldDelegate.value, it doesn't exists anymore
return oldDelegate.color1 != color1
|| oldDelegate.color2 != color2 || oldDelegate.line != line
|| oldDelegate.lineWidth != lineWidth;
}
}
I'm in debug mode so I expect you get a better performance in profile mode

How to populate many the same shapes (rectangles) with Custom Painter (Flutter) in faster way

MyPainter class
class MyPainter extends CustomPainter {
Paint _paint;
**Constructor**
MyPainter(){
_paint = Paint()
..color = Colors.indigo;
}
#override
void paint(Canvas canvas, Size size){
draw one rectangle (size, color, style)
var rect1 = Rect.fromLTWH(0, 0, 20.0,20.0);
color
_paint.color = Color(0xffACCAF6);
style
_paint.style = PaintingStyle.fill;
draw rectangle
canvas.drawRect(rect1, _paint);
}
bool shouldRepaint
#override
bool shouldRepaint(CustomPainter oldDelegate)
return oldDelegate != this;
}
}
img