I'm trying to add border radius to my custom shaped widget using Custom Paint, but I don't know how to add rounded edges to the custom shape.
I achieved the shape, but not the rounded edges.
Below is the code for the custom paint. How can I add border radius to the edges.
``
class RPSCustomPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
Paint paint0 = Paint()
..color = const Color.fromARGB(255, 33, 150, 243)
..style = PaintingStyle.stroke
..strokeWidth = 1.4900000095367432;
Path path0 = Path();
path0.moveTo(3.03, 197.85);
path0.quadraticBezierTo(0.87, 47.28, 1.9, 1.36);
path0.lineTo(207.0, 2.0);
path0.lineTo(170.24, 197.9);
path0.quadraticBezierTo(16.26, 197.13, 3.03, 197.85);
canvas.drawPath(path0, paint0);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
``
you can use drawRRect() to draw the border radius for corners of your shape.
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - gap - smallMarkWidth - 15,gap * 8,gap + 70,gap * 5,),Radius.circular(15.0)),backgroundPaint);
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?
I have a rect that could be positioned anywhere in the 2d coordinate space. This rect will ultimately represent the bounds of an elaborate path (map) comprised of many points. When I draw the path, I don't want the stroke to scale along with the map coordinate, so I intend to use Path.transform to scale and translate without effecting the stroke. Because of this, I want to be able to do this using only transform operations on the canvas (or transform matrix).
To simplify the problem I am using using only a rect in my current example.
I want to do two things to the rect.
Scale it by some scale value.
Center the rect in the view (based on the Size passed in the paint method).
I believe that I need to compensate for the scale when translating but I am not sure how.
Here is my complete app code, I have comments that I hope clarify my intentions.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#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("Custom Painter Test"),
),
body: Container(
width: double.infinity,
height: double.infinity,
child: CustomPaint(
painter: MyCustomPainter(),
),
),
);
}
}
class MyCustomPainter extends CustomPainter {
MyCustomPainter() : super();
#override
void paint(Canvas canvas, Size size) {
var rect = Rect.fromLTRB(-14, -20 , 200.0, 80.0);
var centerX = size.width / 2;
var centerY = size.height / 2;
var centeringXOffset = centerX - rect.center.dx;
var centeringYOffset = centerY - rect.center.dy;
var scale = 2.0;
canvas.save();
canvas.translate(-rect.center.dx, -rect.center.dy); // translate the rect so it is at 0,0 to scale from center of rect
canvas.scale(scale);
canvas.translate(rect.center.dx, rect.center.dy); // translate the rect to original position
canvas.translate(centeringXOffset, centeringYOffset); // translate to center the rect
// draw the rect with border
canvas.drawRect(rect, Paint()..color = Colors.red);
var innerRect = rect.deflate(3);
canvas.drawRect(innerRect, Paint()..color = Colors.white.withOpacity(0.8));
// draw the rect center point
canvas.drawCircle(Offset(rect.center.dx,rect.center.dy) , 3.0 , Paint()..color = Colors.red);
// draw the origincal center point of the view
canvas.restore();
canvas.drawCircle(Offset(centerX , centerY) , 4.0 , Paint()..color = Colors.black.withOpacity(0.32));
}
#override
bool shouldRepaint(MyCustomPainter oldDelegate) => true;
}
when using Matrix4 api you can apply two approaches: the first one is a generic matrix multiplication so you can combine translation, scale and rotation matrices or direct matrix scaling and translating which can be somehow tricky (as you found out when scaling and translating the Canvas in the code you posted)
below code uses those two approaches: the red shape uses matrix multiplication, the orange one uses direct matrix scaling and translating (note that for your particular case you can use rectToRect2() simplified method too)
// your widget code:
final dragDetails = ValueNotifier(DragUpdateDetails(globalPosition: Offset.zero, primaryDelta: 0.0));
#override
Widget build(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: (d) => dragDetails.value = d,
child: CustomPaint(
painter: FooPainter(dragDetails),
child: Center(child: Text('move your finger up and down', textScaleFactor: 2)),
),
);
}
// custom painter code
class FooPainter extends CustomPainter {
final ValueNotifier<DragUpdateDetails> dragDetails;
FooPainter(this.dragDetails) : super(repaint: dragDetails);
double scale = 4.0;
#override
void paint(Canvas canvas, Size size) {
final painterRect = Offset.zero & size;
canvas.drawRect(painterRect, Paint()..color = Colors.black26);
final center1 = Offset(size.width / 2, size.height / 3);
final center2 = Offset(size.width / 2, size.height * 2 / 3);
canvas.drawPoints(PointMode.lines, [
Offset(0, center1.dy), Offset(size.width, center1.dy),
Offset(0, center2.dy), Offset(size.width, center2.dy),
painterRect.topCenter, painterRect.bottomCenter
], Paint()..color = Colors.black38);
final scaleFactor = pow(2, -dragDetails.value.primaryDelta / 128);
scale *= scaleFactor;
final rect = Rect.fromLTWH(14, 20, 20, 28);
final path = Path()
..fillType = PathFillType.evenOdd
..addOval(Rect.fromCircle(center: rect.bottomLeft, radius: 8))
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(3)))
..addOval(Rect.fromCircle(center: rect.center, radius: 3));
Matrix4 matrix;
Path transformedPath;
// first solution
final tranlationMatrix = _translate(center1 - rect.center);
final scaleMatrix = _scale(scale, center1);
matrix = scaleMatrix * tranlationMatrix;
transformedPath = path.transform(matrix.storage);
canvas.drawPath(transformedPath, Paint()..color = Colors.red);
canvas.drawPath(transformedPath, Paint()..style = PaintingStyle.stroke ..strokeWidth = 4 ..color = Colors.white);
// second solution
// you can also use simplified rectToRect2() that "fills" src rect into dst one
matrix = rectToRect(rect, Rect.fromCenter(center: center2, width: scale * rect.width, height: scale * rect.height));
transformedPath = path.transform(matrix.storage);
canvas.drawPath(transformedPath, Paint()..color = Colors.orange);
canvas.drawPath(transformedPath, Paint()..style = PaintingStyle.stroke ..strokeWidth = 4 ..color = Colors.white);
canvas.drawPath(path, Paint()..color = Colors.deepPurple);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Matrix4 _translate(Offset translation) {
var dx = translation.dx;
var dy = translation.dy;
return Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
Matrix4 _scale(double scale, Offset focalPoint) {
var dx = (1 - scale) * focalPoint.dx;
var dy = (1 - scale) * focalPoint.dy;
return Matrix4(scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
/// Return a scaled and translated [Matrix4] that maps [src] to [dst] for given [fit]
/// aligned by [alignment] within [dst]
///
/// For example, if you have a [CustomPainter] with size 300 x 200 logical pixels and
/// you want to draw an expanded, centered image with size 80 x 100 you can do the following:
///
/// ```dart
/// canvas.save();
/// var matrix = sizeToRect(imageSize, Offset.zero & customPainterSize);
/// canvas.transform(matrix.storage);
/// canvas.drawImage(image, Offset.zero, Paint());
/// canvas.restore();
/// ```
///
/// and your image will be drawn inside a rect Rect.fromLTRB(70, 0, 230, 200)
Matrix4 sizeToRect(Size src, Rect dst, {BoxFit fit = BoxFit.contain, Alignment alignment = Alignment.center}) {
FittedSizes fs = applyBoxFit(fit, src, dst.size);
double scaleX = fs.destination.width / fs.source.width;
double scaleY = fs.destination.height / fs.source.height;
Size fittedSrc = Size(src.width * scaleX, src.height * scaleY);
Rect out = alignment.inscribe(fittedSrc, dst);
return Matrix4.identity()
..translate(out.left, out.top)
..scale(scaleX, scaleY);
}
/// Like [sizeToRect] but accepting a [Rect] as [src]
Matrix4 rectToRect(Rect src, Rect dst, {BoxFit fit = BoxFit.contain, Alignment alignment = Alignment.center}) {
return sizeToRect(src.size, dst, fit: fit, alignment: alignment)
..translate(-src.left, -src.top);
}
Matrix4 rectToRect2(Rect src, Rect dst) {
final scaleX = dst.width / src.width;
final scaleY = dst.height / src.height;
return Matrix4.identity()
..translate(dst.left, dst.top)
..scale(scaleX, scaleY)
..translate(-src.left, -src.top);
}
EDIT
you can further simplify rectToRect2 method to:
Matrix4 pointToPoint(double scale, Offset srcFocalPoint, Offset dstFocalPoint) {
return Matrix4.identity()
..translate(dstFocalPoint.dx, dstFocalPoint.dy)
..scale(scale)
..translate(-srcFocalPoint.dx, -srcFocalPoint.dy);
}
and use it like this:
matrix = pointToPoint(scale, rect.center, center2);
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;
}
}
I am trying to get screen size in flutter inside a custom class which donot have build method in it. How can i get screen size without using buildcontext class?
The following code :
class ShapesPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
BuildContext context;
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
final paint = Paint();
paint.color = Colors.deepOrange;
var center = Offset(size.width / 2, size.height / 2);
print(height);
print(width);
Rect rect = Rect.fromLTWH(0.0, 0.0, width, height);
canvas.drawRect(rect, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
gives following error :
The following assertion was thrown during paint():
'package:flutter/src/widgets/media_query.dart': Failed assertion: line
689 pos 12: 'context != null': is not true.
use
MediaQueryData.fromWindow(WidgetsBinding.instance.window);
you can use it to get MediaQuery without need context
it depend on instance of window to get size information
You can directly pass the screen's width and height as a parameter for the widget ShapesPainter if that is all what you need.
Solution Code:
class ShapesPainter extends CustomPainter {
final double width;
final double height;
ShapesPainter({this.width,this.height});
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.color = Colors.deepOrange;
var center = Offset(size.width / 2, size.height / 2);
print(height);
print(width);
Rect rect = Rect.fromLTWH(0.0, 0.0, width, height);
canvas.drawRect(rect, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Usage:
// Wherever you'll be using it
ShapesPainter(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
)
In the following way, you can get the device screen size without declaring them in the build method.
void paint(BuildContext context)
{
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
}
And you can access them inside other file build methods after importing this method.
paint(context)