Creating Wave Shapes in Flutter with Custom Clip Paths
In this post, we will explore how to create wave shapes in Flutter using the ClipPath
widget. We will start with a simple Container
widget, which we will shape into a wave. The ClipPath
widget in Flutter allows us to design various shapes, and by using it, we can create wave patterns that make our user interfaces visually appealing.

Starting Code
We start with a simple Container
widget positioned at the top of the screen with a height
of 200
, see the following code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Different shapes in Flutter ',
home: Scaffold(
body: Container(
color: Theme.of(context).primaryColor,
height: 200,
),
),
);
}
}
Our Container
widget is wrapped by the Scaffold
widget and it produces the following screen:

Create our wave
To create our wave we have to transform the Container
widget, we can do this by the Container
widget with the ClipPath
widget and provide a custom clipper. This clipper will define the shape of the ClipPath
widget, allowing us to create our wave.
home: Scaffold(
body: ClipPath(
clipper: WaveClipper(),
child: Container(
color: Theme.of(context).primaryColor,
height: 200,
),
),
),
As you can see in the above code we introduce a custom clipper inside the ClipPath
widget called WaveClipper
. The WaveClipper
widget looks as follows:
class WaveClipper extends CustomClipper<Path> {
@override
getClip(Size size) {
var path = Path();
path.lineTo(0, 175);
// The values of the calculations would be path.quadraticBezierTo(100, 75, 200, 150) if the height is 200 and the width is 400;
path.quadraticBezierTo(size.width * 0.25, size.height * 0.50 - 25,
size.width * 0.50, size.height * 0.75);
// The values of the calculations would be path.quadraticBezierTo(300, 225, 400, 150) if the height is 200 and the width is 400;
path.quadraticBezierTo(
size.width * 0.75, size.height + 25, size.width, size.height * 0.75);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
The WaveClipper
class overrides the getClip
function to define the custom shape of the ClipPath
widget. It takes a Size
parameter, representing the size of the widget being clipped.
Within the getClip
function, a Path
object is created to define the shape of the clip path. The Path
class provides functions for creating various geometric shapes and paths.
To create the wave shape, the Path
object uses the lineTo
function to draw straight lines and the quadraticBezierTo
function to draw curves.
Two quadraticBezierTo
calls are made in the code snippet. The first call creates the bottom-left curve of the wave shape, while the second call creates the bottom-right curve. By adjusting the control and end points of these calls, you can modify the shape and position of the curve.
After defining the path, the path.close()
function is called to close the path and create a closed shape.
The getClip
function returns the final Path
object, representing the custom shape of the clip path.
Additionally, the shouldReclip
function is overridden in the WaveClipper
class to determine whether the clip path needs to be recomputed when the widget is updated. In this case, the function always returns false
, indicating that the clip path doesn't need to be updated.

Add additional waves
If you want to have four waves instead of two, you can modify the WaveClipper
class to create additional quadraticBezierTo
calls and adjust the control and end points accordingly. This will allow you to achieve the desired four-wave shape.
Here's an example of how you can modify the code to create four waves:
class WaveClipper extends CustomClipper<Path> {
@override
getClip(Size size) {
var path = Path();
path.lineTo(0, size.height * 0.75);
path.quadraticBezierTo(
size.width * 0.125, size.height * 0.50,
size.width * 0.25, size.height * 0.75);
path.quadraticBezierTo(
size.width * 0.375, size.height * 0.95,
size.width * 0.50, size.height * 0.75);
path.quadraticBezierTo(
size.width * 0.625, size.height * 0.50,
size.width * 0.75, size.height * 0.75);
path.quadraticBezierTo(
size.width * 0.875, size.height * 0.95,
size.width, size.height * 0.75);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
In this modified code, we added two additional quadraticBezierTo
calls to create the extra waves. Each call represents the control and end points for a particular wave curve.
To evenly distribute the waves, we divided the width of the container into four sections (size.width * 0.25
, size.width * 0.50
, size.width * 0.75
, and size.width
). By adjusting the y
coordinates of the control and end points, you can control the height and shape of each wave.
Feel free to adjust the values and experiment with different control and end points to create the exact shape and style of the four waves you desire.

Conclusion
In this post, we learned how to create wave shapes in Flutter using custom clip paths. By using the quadraticBezierTo
function, we were able to define the curves of the waves. We also explored how to add more waves or modify the shape by adjusting the control and end points. Flutter's clip paths offer endless possibilities for designing unique shapes in your applications.