How can i create custom shape widget in flutter? which is dynamically render in all mobile devices - flutter

I want to add a custom profile avatar to one of my projects. I tried with the custom painter & clipper. But, when I test in different devices the shape & size are not as my designer gave to me.
This is my painter class.
import 'package:flutter/material.dart';
class ProfileBox extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint();
paint.color = Colors.grey;
paint.style = PaintingStyle.fill;
final path = Path();
path.moveTo(25, 4);
path.quadraticBezierTo(size.width / 2, -4, size.width - 25, 4);
path.quadraticBezierTo(size.width - 10, 10, size.width - 4, 25);
path.quadraticBezierTo(
size.width + 4, size.height / 2, size.width - 4, size.height - 25);
path.quadraticBezierTo(
size.width - 10, size.width - 10, size.width - 25, size.height - 4);
path.quadraticBezierTo(
size.width / 2, size.height + 4, 25, size.height - 4);
path.quadraticBezierTo(10, size.height - 10, 4, size.height - 25);
path.quadraticBezierTo(-4, size.height / 2, 4, 25);
path.quadraticBezierTo(10, 10, 25, 4);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return this != oldDelegate;
}
}
This is my clipper class.
import 'package:invoice_app/constants/globals.dart';
class ProfileClipper extends CustomClipper<Path> {
ProfileClipper({#required this.profileSize});
final ProfileSize profileSize;
#override
Path getClip(Size size) {
final path = Path();
path.moveTo(23, 2);
path.quadraticBezierTo(size.width / 2, -4, size.width - 23, 2);
path.quadraticBezierTo(size.width - 8, 8, size.width - 2, 23);
path.quadraticBezierTo(
size.width + 4, size.height / 2, size.width - 2, size.height - 23);
path.quadraticBezierTo(
size.width - 8, size.width - 8, size.width - 23, size.height - 2);
path.quadraticBezierTo(
size.width / 2, size.height + 4, 23, size.height - 2);
path.quadraticBezierTo(8, size.height - 8, 2, size.height - 23);
path.quadraticBezierTo(-4, size.height / 2, 2, 23);
path.quadraticBezierTo(8, 8, 23, 2);
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}
And, I'm using it in UI with the below widget.
painter: ProfileBox(),
child: Container(
padding: const EdgeInsets.all(5),
height: 100,
width: 100,
child: ClipPath(
clipper: ProfileClipper(profileSize: ProfileSize.smallProfile),
child: Container(
// if image url is null and want to display icon or svg then add padding
padding: _imageFile != null
? const EdgeInsets.all(0)
: const EdgeInsets.all(13),
// color: Colors.blue,
child: Image(
// if image url is null then display icon or svg
image: _imageFile != null
? FileImage(_imageFile)
: AssetImage(
'assets/defaultbusiness.png',
),
fit: BoxFit.fill,
),
),
),
),
),

Related

How do I shape an image as i desire in flutter

I want a bottom border in the above given way for an image
I have used ClipRRect for the same
ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(50),
bottomRight: Radius.circular(50)),
// Image border
child: SizedBox.fromSize(
size: Size.fromRadius(48), // Image radius
child: Image.network(data['displayPic'],
fit: BoxFit.cover),
),
)),
You could create CustomClipper , I will share an example Code which will achieve the way you want to shape the image , you could change radius according to your need.
class ClipPathClass extends CustomClipper<Path> {
#override
Path getClip(Size size) {
double radius = 50;
var path = Path();
path.lineTo(0.0, size.height - 30);
var firstControlPoint = Offset(size.width / 4, size.height);
var firstPoint = Offset(size.width / 2, size.height);
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
firstPoint.dx, firstPoint.dy);
var secondControlPoint = Offset(size.width - (size.width / 4), size.height);
var secondPoint = Offset(size.width, size.height - 30);
path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
secondPoint.dx, secondPoint.dy);
path.lineTo(size.width, 0.0);
path.lineTo(size.width - radius, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Now use this ClipPathClass like ,
ClipPath(
clipper: ClipPathClass(),
child: //Your Image Container here
)

Slicing a circular container in which each slice behaves like a tab in Flutter

I am trying to achieve a circular widget in which each slice represents a tab and having a separation line between each slice coming out from the centre of the circle as shown in the image.
I have tried bunch of packages and I found the following package fulfilling the purpose to some extent. https://pub.dev/packages/circular_widgets
I have also tried with Clipping and Custom painting API, but couldn't achieve what i am looking for.
I made custom painter with widgets
I hope this help you
class CircleCustomPainer extends CustomPainter {
var count = 6;
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
paint.color = Colors.red;
Paint paint1 = Paint();
paint1.color = Colors.white;
paint1.strokeWidth = 4;
paint1.style = PaintingStyle.stroke;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2), size.height / 2, paint);
Path path = Path();
path.moveTo(size.width / 2, size.height / 2);
for (var i = 0; i < count; i++) {
var o = (2 * i * math.pi) / count;
var x = 150 * math.cos(o) + (size.width / 2);
var y = 150 * math.sin(o) + (size.height / 2);
path.lineTo(x, y);
path.moveTo(size.width / 2, size.height / 2);
}
canvas.drawPath(path, paint1);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
this is widget creator for menu
Widget getWidgets(int count, Size size) {
List<Widget> list = [];
for (var i = 0; i < count; i++) {
var o = (2 * i * math.pi) / count;
o = o + ((360 / count) / 57.2958) / 2;
var x = (size.width / 3) * math.cos(o) + (size.width / 2);
var y = (size.width / 3) * math.sin(o) + (size.height / 2);
list.add(Positioned.fromRect(
rect: Rect.fromCenter(center: Offset(x, y), height: 60, width: 60),
child: Column(
children: [
Container(
color: Colors.black,
width: 60,
height: 60,
),
],
),
));
}
return Container(
width: 300,
height: 300,
alignment: Alignment.center,
child: Stack(
children: list,
),
);
}
you can used like this
SizedBox(
width: 300,
height: 300,
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
size: Size(300, 300),
painter: CircleCustomPainer(),
),
Center(child: getWidgets(6, Size(300, 300)))
],
),
)
I will probably create a youtube video for that

Flutter reverse clipper direction

I need to show triangle type shape container which is almost complete but there is one issue its showing the triangle shape in opposite direction my code
class ClipPathClass extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var roundnessFactor = 50.0;
var path = Path();
path.moveTo(0, size.height * 0.33);
path.lineTo(0, size.height - roundnessFactor);
path.quadraticBezierTo(0, size.height, roundnessFactor, size.height);
path.lineTo(size.width - roundnessFactor, size.height);
path.quadraticBezierTo(
size.width, size.height, size.width, size.height - roundnessFactor);
path.lineTo(size.width, roundnessFactor * 2);
path.quadraticBezierTo(size.width - 10, roundnessFactor,
size.width - roundnessFactor * 1.5, roundnessFactor * 1.5);
path.lineTo(
roundnessFactor * 0.6, size.height * 0.33 - roundnessFactor * 0.3);
path.quadraticBezierTo(
0, size.height * 0.33, 0, size.height * 0.33 + roundnessFactor);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
This is how i am showing this
ClipPath(
clipper: RoundedDiagonalPathClipper(),
child: Container(
height: 320,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30.0)),
color: Colors.orange,
),
child: null,
),
),
Out put is showing like this
But i need to show the upper part like this just reverse it. I play with everything but it's not reversing.
I implemented _getY function so that there is no bend in the corner. And I added an equalization variable to make look better.
Reference: Paths in Flutter: A Visual Guide.
Screenshot
RoundedDiagonalPathClipper.dart
class RoundedDiagonalPathClipper extends CustomClipper<Path> {
double _getY(double x) {
return x * 0.33;
}
#override
Path getClip(Size size) {
var roundnessFactor = 50.0;
var equalization = 10.0;
var path = Path();
path.moveTo(0, roundnessFactor);
path.lineTo(0, size.height - roundnessFactor);
path.quadraticBezierTo(0, size.height, roundnessFactor, size.height);
path.lineTo(size.width - roundnessFactor, size.height);
path.quadraticBezierTo(
size.width, size.height, size.width, size.height - roundnessFactor);
path.lineTo(size.width, _getY(size.width) + roundnessFactor - equalization);
path.quadraticBezierTo(
size.width,
_getY(size.width),
size.width - roundnessFactor + equalization,
_getY(size.width - roundnessFactor + equalization));
path.lineTo(
roundnessFactor + equalization, _getY(roundnessFactor + equalization));
path.quadraticBezierTo(0, 0, 0, roundnessFactor + equalization);
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Edit: Adding shadow
I made the shadow version by using the gist:coman3-ClipShadowPath.dart.
ClipShadowPath.dart
#immutable
class ClipShadowPath extends StatelessWidget {
final Shadow shadow;
final CustomClipper<Path> clipper;
final Widget child;
ClipShadowPath({
required this.shadow,
required this.clipper,
required this.child,
});
#override
Widget build(BuildContext context) {
return CustomPaint(
key: UniqueKey(),
painter: _ClipShadowShadowPainter(
clipper: this.clipper,
shadow: this.shadow,
),
child: ClipPath(child: child, clipper: this.clipper),
);
}
}
class _ClipShadowShadowPainter extends CustomPainter {
final Shadow shadow;
final CustomClipper<Path> clipper;
_ClipShadowShadowPainter({required this.shadow, required this.clipper});
#override
void paint(Canvas canvas, Size size) {
var paint = shadow.toPaint();
var clipPath = clipper.getClip(size).shift(shadow.offset);
canvas.drawPath(clipPath, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Usage
ClipShadowPath(
shadow: Shadow(blurRadius: 20.0, color: Colors.black54),
clipper: RoundedDiagonalPathClipper(),
child: Container(color: Colors.orange),
)
just apply Matrix transformation on Path, so you will free to do any changes on path, it will flip vertically any path
how to use:
clipper: ClipPathClass(),
or if need to reverse
clipper: ClipPathClass(flip: true),
class ClipPathClass extends CustomClipper<Path> {
final roundnessFactor = 50.0;
final bool flip;
ClipPathClass({this.flip = false});
#override
Path getClip(Size size) {
final path = Path();
path.moveTo(0, size.height * 0.33);
path.lineTo(0, size.height - roundnessFactor);
path.quadraticBezierTo(0, size.height, roundnessFactor, size.height);
path.lineTo(size.width - roundnessFactor, size.height);
path.quadraticBezierTo(size.width, size.height, size.width, size.height - roundnessFactor);
path.lineTo(size.width, roundnessFactor * 2);
path.quadraticBezierTo(size.width - 10, roundnessFactor, size.width - roundnessFactor * 1.5, roundnessFactor * 1.5);
path.lineTo(roundnessFactor * 0.6, size.height * 0.33 - roundnessFactor * 0.3);
path.quadraticBezierTo(0, size.height * 0.33, 0, size.height * 0.33 + roundnessFactor);
return flip ? _flip(path, size.width) : path;
}
Path _flip(Path path, double width) {
final m = Matrix4.identity()
..translate(width)
..rotateY(math.pi);
return path.transform(m.storage);
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
I won't explain the change line by line, I tried to keep the round corner uniform on the four corners.
Here your are:
class RoundedDiagonalPathClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var roundnessFactor = 50.0;
var path = Path();
path.moveTo(0, roundnessFactor);
path.lineTo(0, size.height - roundnessFactor);
path.quadraticBezierTo(0, size.height, roundnessFactor, size.height);
path.lineTo(size.width - roundnessFactor, size.height);
path.quadraticBezierTo(
size.width, size.height, size.width, size.height - roundnessFactor);
path.lineTo(size.width, size.height * 0.33 + roundnessFactor * 0.5);
path.quadraticBezierTo(
size.width,
size.height * 0.33,
size.width - roundnessFactor,
size.height * 0.33 - roundnessFactor * 0.5);
path.lineTo(roundnessFactor, 0);
path.quadraticBezierTo(0, 0, 0, roundnessFactor);
return path;
}
The result will be this one:

How to fill color inside a shape created using CustomPainter drawPath?

So, I've created a shape using drawPath and drawArc from CustomPainter, the PaintingStyle is stroke, but when I change it to fill, it only fills the arcs and not the whole shape.
I want to fill the shape I created with a color, so how can I fill the shape with a particular color?
class CustomShapeCard extends CustomPainter {
CustomShapeCard({#required this.strokeWidth, #required this.color});
final double strokeWidth;
final Color color;
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
var path = Path();
path.moveTo(size.width * 0.1, size.height * 0.2);
path.lineTo(size.width * 0.1, size.height * 0.9);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.2) - 14, size.height * 0.9),
height: 50,
width: 50,
),
math.pi / 2,
math.pi / 2,
false,
paint,
);
path.moveTo((size.width * 0.2) - 14, (size.height * 0.9) + 25);
path.lineTo((size.width * 0.9) - 25, size.height * 0.9 + 25);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.9) - 25, size.height * 0.9),
height: 50,
width: 50,
),
math.pi / 2,
-math.pi / 2,
false,
paint,
);
path.moveTo((size.width * 0.9), (size.height * 0.9));
path.lineTo(size.width * 0.9, size.height * 0.35);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.9) - 25, size.height * 0.35),
height: 50,
width: 50,
),
-math.pi / 2,
math.pi / 2,
false,
paint,
);
path.moveTo((size.width * 0.9) - 25, (size.height * 0.35) - 25);
path.lineTo(size.width * 0.25, (size.height * 0.35) - 25);
canvas.drawPath(path, paint);
canvas.drawArc(
Rect.fromCenter(
center: Offset((size.width * 0.25), (size.height * 0.35) - 50),
height: 50,
width: 50,
),
math.pi / 2,
math.pi / 3,
false,
paint,
);
path.moveTo((size.width * 0.25) - 20, (size.height * 0.35) - 35);
path.lineTo(size.width * 0.1, size.height * 0.2);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
When the PaintingStyle is stroke, I get this,
When I change PaintingStyle to fill, I get,
To fill a shape like this with a color,
the arcToPoint() should be used instead of drawArc().

Draw Custom shape Flutter

I'm trying to draw the shape in this image
The shape that I want to draw
Put I can't get the same result by my code :
CustomShapeClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final Path path = Path()
..moveTo(0, size.height * 0.6)
..quadraticBezierTo(
size.width * 0.7 , size.height - (size.height * 0.1) ,
size.width, size.height * 0.8
)
..lineTo(size.width, 0)
..lineTo(0, 0)
..close();
return path;
}
#override
bool shouldReclip(CustomClipper oldClipper) => true;
}
I get this result My Result
Kindly guide me for this.
Thanks in advance
I managed to draw something similar using cubicTo instead of quadraticBezierTo. A simple example for what you need:
final Path path = Path()
..moveTo(0, size.height * 0.6)
..lineTo(size.width * 0.7 - (size.width * 0.05),
size.height - 2 * (size.height * 0.1))
..cubicTo(
size.width * 0.7 - (size.width * 0.01),
size.height - 1.88 * (size.height * 0.1),
size.width * 0.7 + (size.width * 0.01),
size.height - 1.88 * (size.height * 0.1),
size.width * 0.7 + (size.width * 0.05),
size.height - 2 * (size.height * 0.1))
..lineTo(size.width, size.height * 0.7)
..lineTo(size.width, 0)
..lineTo(0, 0)
..close();
I know that there are a lot of numbers, but you can extract some points as separate variables for a better readability.
Practically instead of drawing a Quadric Bezier, we draw 2 lines and the curve between them.
Also you can add clipBehavior: Clip.antiAliasWithSaveLayer to your ClipPath for a smooth drawing
Try this and let me know if it works for you.
In your widget body you can have a build method looking similar to this
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
ClipPath(
clipper: BottomEndClipper(),
child: Container(
height: MediaQuery.of(context).size.height * .5,
decoration: BoxDecoration(
//Your gradient or own color
color: Colors.purple,
),
),
),
],
),
);
}
}
And your custom clipper looking like this
class BottomEndClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.lineTo(0, size.height - 80);
path.lineTo(size.width * .7, size.height - 10);
path.quadraticBezierTo(
size.width * .8, size.height, size.width * .95, size.height * .90);
path.lineTo(size.width, size.height*.87);
path.lineTo(size.width, 0);
path.close();
return path;
}
//Should return false if you don't wish to redraw every time
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}