This question already has an answer here:
How to draw custom shapes in flutter
(1 answer)
Closed last month.
I am wondering if is there any efficient way to create a custom shape like this, where I can change the color of each segment?
You can do this with CustomClipper like this:
class CustomDraw2 extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.moveTo(0, 0);
path.cubicTo(
0, 0, size.height * 0.3, 0, size.height * 0.4, size.height * 0.4);
path.cubicTo(
size.height * 0.4,
size.height * 0.4,
size.height * 0.4,
size.height * 0.66,
size.height * 0.9,
size.height * 0.7,
);
path.cubicTo(
size.height * 0.9,
size.height * 0.7,
size.height * 1.4,
size.height * 0.7,
size.height * 1.5,
size.height,
);
path.lineTo(0, size.height);
path.lineTo(0, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
and use it like this:
Container(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(16),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: Stack(
children: [
Container(
height: 100,
width: double.infinity,
color: Colors.green,
),
SizedBox(
height: 100,
width: 300,
child: ClipPath(
clipper: CustomDraw2(),
child: Container(
color: Colors.blueAccent,
),
),
),
],
),
)
The best thing you can use is Flutter Shape Maker
Fun Fact: If you know some basics how to draw, it will be fun! It can generate the custom clipper code for you!
Shape Maker Official Website
Reference Video 1 (Basic and Old)
Reference Video 2
Reference Video 3
Been using for some production based application, and working very nicely
Related
I have a complex design, I don't know what to do with flutter, is there any help on how to implement this design?
Usually to create complex designs I prefer using Flutter Shape Maker.
All you need is to upload your svg picture and it will convert it to CustomPaint code in flutter. Perhaps this may help you.
I have tried to achieve a shape and the center painter here. Few things, I am able to achieve but for complete part, I think you need to use pie chart https://pub.dev/packages/syncfusion_flutter_charts specially Doughnut type.
Please check my sample as below:
This painter is used to prepare the pie.
class WheelPainter extends CustomPainter {
Path getWheelPath(double wheelSize, double fromRadius, double toRadius) {
return new Path()
..moveTo(wheelSize, wheelSize)
..arcTo(
Rect.fromCircle(
radius: wheelSize, center: Offset(wheelSize, wheelSize)),
fromRadius,
toRadius,
false)
..close();
}
Paint getColoredPaint(Color color) {
Paint paint = Paint();
paint.color = color;
return paint;
}
#override
void paint(Canvas canvas, Size size) {
double wheelSize = 150;
double nbElem = 6;
double radius = (2 * pi) / nbElem;
// canvas.drawPath(getWheelPath(wheelSize, 0, radius), getColoredPaint(Colors.red));
canvas.drawShadow(getWheelPath(wheelSize, radius * 0.5, radius * 2).shift(Offset(0, -10)), Colors.black, 10.0, true);
canvas.drawPath(getWheelPath(wheelSize, radius * 0.5, radius * 2),
getColoredPaint(Colors.purple));
// canvas.drawPath(getWheelPath(wheelSize, radius * 2, radius), getColoredPaint(Colors.blue));
canvas.drawShadow(getWheelPath(wheelSize, radius * 2.7, radius * 1.7).shift(Offset(0, -10)), Colors.black, 10.0, true);
canvas.drawPath(getWheelPath(wheelSize, radius * 2.7, radius * 1.7),
getColoredPaint(Colors.green));
// canvas.drawPath(getWheelPath(wheelSize, radius * 4, radius), getColoredPaint(Colors.yellow));
canvas.drawShadow(getWheelPath(wheelSize, radius * 4.6, radius * 1.7).shift(Offset(0, -10)), Colors.black, 10.0, true);
canvas.drawPath(getWheelPath(wheelSize, radius * 4.6, radius * 1.7),
getColoredPaint(Colors.orange));
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
Below is the sample to use it.
Stack(alignment: Alignment.center, children: [
CustomPaint(
size: Size(300, 300),
painter: WheelPainter(),
),
Container(
width: 150,
height: 150,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.white),
),
Container(
width: 110,
height: 110,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 10.0,
blurStyle: BlurStyle.outer,
spreadRadius: 3.0,
offset: Offset(1.0,1.0)
)
]),
)
]),
This is how it will look like.
Hope it may help you.
You can take two container in Stack Widget
and implement customPaint
customPaint
You can try packges from pub.dev for this and you can customise as you need in app this designs.
syncfusion_flutter-chart
Try above package for your requirements
I wanted to create the curved container like widget in the picture below
I used the Custom clipper class and created a similar one which is shown below
Below is the WaveClipper class I used to create the curved widget
class WaveClipper extends CustomClipper<Path> {
#override
getClip(Size size) {
var path = new Path();
path.lineTo(0, size.height / 5.25);
var firstControlPoint = Offset(size.width / 120, size.height / 2);
var firstEndPoint = Offset(size.width / 1.5, size.height / 2.5 );
var thirdControlPoint = Offset(size.width/1.025, size.height / 2.8 );
var thirdEndPoint = Offset(size.width, size.height / 1.8 );
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy, firstEndPoint.dx,
firstEndPoint.dy);
path.quadraticBezierTo(thirdControlPoint.dx, thirdControlPoint.dy, thirdEndPoint.dx,
thirdEndPoint.dy);
path.lineTo(size.width, size.height/3 );
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
Please help me to achieve the desired output !!!
By wrapping the ClipPath widget with the container you can create your design see below code.
Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.blue),
color: Colors.blue),
child: ClipPath(
child: Container(
height: 200,
width: 200,
color: Colors.white,
),
clipper: WaveClipper()),
),
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,
),
),
),
),
),
I would like to operate some perspective on a ClipPath overlay with Flutter.
I reproduced inverted Clip Oval from Flutter: inverted ClipOval which works fine.
Then i would like to operate a perspective on this overlay:
For now i use a Transform widget but the "grey background" gets also rotated.I would like the background to expand on all screen left.
I think i should rotate only in InvertedRectClipper but i can't find a way to do something similar as alignment: FractionalOffset.center which tell where is the origin to rotate on.
Anyone have a tips for this ?
Actual screenshot :
Background is not entirely grey
Full code to test :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: SafeArea(
child: Stack(
children: <Widget>[
Container(
color: Colors.white,
),
Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.0005) // perspective
..rotateX(-0.9),
child: ClipPath(
clipper: InvertedRectClipper(),
child: Container(
color: Color.fromRGBO(0, 0, 0, 0.5),
),
),
)
],
),
),
));
}
class InvertedRectClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
return Path()
..addRect(Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: size.width / 2,
height: size.height / 2))
..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..fillType = PathFillType.evenOdd;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
The key is to only transform the path you need to transform, not the whole widget.
Here is a sample :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: SafeArea(
child: Stack(
children: <Widget>[
Container(
color: Colors.blue,
),
ClipPath(
clipper: InvertedRectClipper(),
child: Container(
color: Colors.black.withOpacity(0.5),
),
)
],
),
),
));
}
class InvertedRectClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
// First rectangle, to be transformed
var path = Path()
..fillType = PathFillType.evenOdd
..addRect(Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: size.width / 2,
height: size.height / 2));
// Transform matrix
final matrix = Matrix4.identity()
..setEntry(3, 2, 0.0005)
..rotateX(-0.9);
path = path.transform(matrix.storage);
// Outer rectangle, straight
path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
// Return path
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
Here is the result
You'll 'just' have to adapt the transformation to your convenance.
** EDIT **
If you need the transformation to be centered relative to your rectangle, an easy way is to draw the rectangle centered on the origin, apply the transformation, then translate it to be centered to the screen :
Path getClip(Size size) {
// First rectangle, to be transformed, centered at the origin
var path = Path()
..fillType = PathFillType.evenOdd
..addRect(Rect.fromCenter(
center: Offset(0, 0),
width: size.width / 2,
height: size.height / 2));
// Rotation X
final matrix = Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-0.9);
path = path.transform(matrix.storage);
// Translation to center back the rectangle
path = path.transform(Matrix4.translationValues(size.width / 2, size.height / 2, 0).storage);
// Outer rectangle, straight
path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
// Return path
return path;
}
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;
}