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;
}
Related
How to implement effect like pic below.
I have a rect container(width:400, height:100), I want to clip the blue area of the rect, so I can get the orange area.Attention,
The orange arc is tangent to the bottom line . Could you give the details code?
You can use a Custom Clipper class , to get this type of shape I will share an example code you can change values 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;
}
then use this ClipPathClass() like ,
ClipPath(
clipper: ClipPathClass(),
child: Container(width:400,height: 100,color:Colors.grey)
)
I'm trying to clip a widget using ClipPath CustomClipper -> so far I was able to recreate a wave shape
ClipPath(
clipper: WaveClipper(),
child: CustomWidget(),
);
class WaveClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = new Path();
path.lineTo(
0, size.height);
var firstStart = Offset(size.width / 5, size.height);
var firstEnd = Offset(size.width / 2.25, size.height - 50.0);
path.quadraticBezierTo(
firstStart.dx, firstStart.dy, firstEnd.dx, firstEnd.dy);
var secondStart =
Offset(size.width - (size.width / 3.24), size.height - 105);
var secondEnd = Offset(size.width, size.height - 10);
path.quadraticBezierTo(
secondStart.dx, secondStart.dy, secondEnd.dx, secondEnd.dy);
path.lineTo(
size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false; //if new instance have different instance than old instance
//then you must return true;
}
but what I would need to achieve is this pink shape shown on this image :
the top part does not need to be clipped as it's the edge of the screen, only the bottom
Maybe it's not exactly how you want it, but with small adjustments it stays the same, you have to pay attention to where you left off to start correctly
class CustomClipperImage extends CustomClipper<Path> {
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
#override
getClip(Size size) {
var path = Path();
path.lineTo(0, size.height-70);
path.quadraticBezierTo(0, size.height+40,size.width-40, size.height-
130);
path.quadraticBezierTo(size.width+60, size.height-170,size.width, 0);
path.close();
return path;
}
}
Did you see flutter_custom_clippers package? I think its source code will be helpfull.
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 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);
I have this shape
enter image description here
I want to flip it to be like this
enter image description here
this is the original code
class CustomMenuClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Paint paint = Paint();
paint.color = Colors.white;
final width = size.width;
final height = size.height;
Path path = Path();
path.moveTo(0, 0);
path.quadraticBezierTo(0, 8, 10, 16);
path.quadraticBezierTo(width - 1, height / 2 - 20, width, height / 2);
path.quadraticBezierTo(width + 1, height / 2 + 20, 10, height - 16);
path.quadraticBezierTo(0, height - 8, 0, height);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
this is the github repository
I don't mind if it comes to a half-circle.
I think is should be something like this:
Path getClip(Size size) {
Paint paint = Paint();
paint.color = Colors.white;
final width = size.width;
final height = size.height;
Path path = Path();
path.moveTo(width, 0);
path.quadraticBezierTo(16, 10, 8, 0);
path.quadraticBezierTo(height / 2, width, height / 2 - 20 ,width - 1);
path.quadraticBezierTo( height - 16, 10, height / 2 + 20, height - 16width + 1);
path.quadraticBezierTo(height, 0, height - 8, 0);
path.close();
return path;
}
I would not recommend this, since different devices might reproduce different behaviors. Maybe using size.width and size.height might be a better idea for responsiveness.