Creating Wave Shapes in Flutter with Custom Clip Paths

Flutter Jul 1, 2023

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.

flutter_wave_shaped_container_example

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:

flutter_simple_container

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.

flutter_wave_shaped_container_example

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.

flutter_wave_shaped_container_with_four_waves

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.

Tags