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)
Related
I pin the picture of how it should looks like. I was trying ti use Cnavas, because as I inderstood such things can't be done by simple widget. So I have a question, how can I extract rectangle from this filled rectangle? I saw solution like this:
class TransparantRectanglePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.color = Colors.blue;
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()
..addRRect(RRect.fromLTRBR(100, 100, 300, 300, Radius.elliptical(x: 10, y: 15))),
Path()
..addRect(Rect.fromLTRB(100, 100, 100, 100)
..close(),
),
paint,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
As for the fromLTRBR,
Construct a rounded rectangle from its left, top, right, and bottom edges,
and the same radius in each corner.
And fromLTRB
Construct a rectangle from its left, top, right, and bottom edges.
It is better to use size rather than hard coded value.
If you want round corner, use addRRect,
quadraticBezierTo or relativeQuadraticBezierTo etc.
class TransparentRectanglePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.color = Colors.red;
final Path path1 = Path()
..addRRect(
RRect.fromLTRBR(
0, 0, size.width, size.height, const Radius.elliptical(10, 15)),
);
final Path path2 = Path()
..addRect(
Rect.fromLTRB(0, size.height / 2, size.width / 2, size.height),
);
canvas.drawPath(
Path.combine(PathOperation.difference, path1, path2),
paint,
);
}
#override
bool shouldRepaint(TransparentRectanglePainter oldDelegate) =>
this != oldDelegate;
}
Visit flutter.dev to learn more about CustomPaint widget.
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 want to create custom appbar shape as shown in below image. How we can do such shape using clippath?
Tried code:
class CustomAppBarShape extends CustomClipper<Path> {
#override
getClip(Size size) {
double height = size.height;
double width = size.width;
var path = Path();
path.lineTo(0, size.height);
path.arcToPoint(Offset(size.width, size.height),
radius: Radius.elliptical(30, 10),
);
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return true;
}
}
I created the desired AppBar shape by giving custom shape to the AppBar border, check out the live example here.
If you want to clip the AppBar you can use similar Path in the clipper too but I think giving custom shape to the border is better.
code for custom AppBar border shape:
class CustomAppBarShape extends ContinuousRectangleBorder {
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
double height = rect.height;
double width = rect.width;
var path = Path();
path.lineTo(0, height + width * 0.1);
path.arcToPoint(Offset(width * 0.1, height),
radius: Radius.circular(width * 0.1),
);
path.lineTo(width * 0.9, height);
path.arcToPoint(Offset(width, height + width * 0.1),
radius: Radius.circular(width * 0.1),
);
path.lineTo(width, 0);
path.close();
return path;
}
}
Edit:
For dynamic edge radius update the customAppBarShape with the multiplier multi as constructor argument, and use multi to create outer path.
class CustomAppBarShape extends ContinuousRectangleBorder {
final double multi;
const CustomAppBarShape({this.multi = 0.1});
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
double height = rect.height;
double width = rect.width;
var path = Path();
path.lineTo(0, height + width * multi);
path.arcToPoint(Offset(width * multi, height),
radius: Radius.circular(width * multi),
);
path.lineTo(width * (1 - multi), height);
path.arcToPoint(Offset(width, height + width * multi),
radius: Radius.circular(width * multi),
);
path.lineTo(width, 0);
path.close();
return path;
}
}
Then send your desired value in constructor parameter like so,
appBar: AppBar(
title: Text(widget.title),
shape: const CustomAppBarShape(multi: 0.02),
)
make sure the value of multi is less than 1 for desired shape.
if you still prefer to use clipper instead of custom shape, you can use my code below for clip image.
class BannerClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var height = size.height;
var width = size.width;
double radius = 20;
Path path = Path()
..lineTo(0, height)
..arcToPoint(Offset(20, height - 20), radius: Radius.circular(radius))
..lineTo(width - 20, height - 20)
..arcToPoint(Offset(width, height), radius: Radius.circular(radius))
..lineTo(width, 0)
..close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}
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;
}
}