I’m trying to replicate a custom curved profile banner shape using CustomPaint in Flutter.
This is the desired shape I’m trying to achieve — notice how the bottom of the banner has a smooth inward curve (concave) with nice rounded edges:
However, here’s the shape I’m currently getting — the curve doesn’t match, and the corners are not blending correctly:
and this is my current code
What I need help with: How to match the exact smooth bottom curve of the profile banner as shown in the first image.
Ensuring the edges curve correctly and symmetrically from left to right.
I’m open to using cubicTo, quadraticBezierTo, or even arcToPoint — whichever helps match the reference shape.
Would really appreciate any pointers on fixing the Path logic.
I used CustomClipper and achieved the same output as shown in your image. Try the code below.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.black,
height: 300,
width: 400,
child: Stack(
alignment: Alignment.topCenter,
children: [
ClipPath(
clipper: WaveClipper(),
child: Container(width: 400, height: 300, color: Colors.red),
),
Positioned(
bottom: 15,
child: Container(
height: 130,
width: 130,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
),
],
),
),
),
);
}
class WaveClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double waveHeight = 100;
double waveWidth = 65;
double blackHeight = 90;
double cornerCurve = 40;
final path = Path();
path.moveTo(0, 0);
path.lineTo(0, size.height - blackHeight);
path.quadraticBezierTo(
(size.width / 2) - waveWidth - cornerCurve,
size.height - blackHeight,
(size.width / 2) - waveWidth,
size.height - blackHeight - cornerCurve,
);
path.quadraticBezierTo(
size.width / 2,
size.height - blackHeight - waveHeight,
(size.width / 2) + waveWidth,
size.height - blackHeight - cornerCurve,
);
path.quadraticBezierTo(
(size.width / 2) + waveWidth + cornerCurve,
size.height - blackHeight,
size.width,
size.height - blackHeight,
);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}