Using CustomClipper to draw backgrounds with predefined paths

Reading time: 2 minutes

In this example we gonna learn how to draw interesting backgrounds using CustomClipper with factory paths. This great class allows you to draw whatever form you want. Here we’ll see a few examples that can be used to decorate any widget and give some unique look for your widgets. This is a result we will see:

The code below creates different types we will use in enum ClipType. TypedClipper is the main class that helps to build proper paths for the type we provide. TypedClipperWidget hold uses ClipPath with data we can get from TypedClipper.

enum ClipType { pointed, curved, arcDown, arcUp, triangle, wave }

class TypedClipperWidget extends StatelessWidget {
  final Widget child;
  final ClipType clipType;

  TypedClipperWidget({this.child, this.clipType = ClipType.curved});

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: TypedClipper(clipType),
      child: child,
    );
  }
}

class TypedClipper extends CustomClipper<Path> {
  ClipType clipType;

  TypedClipper(this.clipType);

  @override
  Path getClip(Size size) {
    Path path = Path();
    switch (clipType) {
      case ClipType.pointed:
        createPointedTriangle(size, path);
        break;
      case ClipType.arcDown:
        createBezierArcDown(size, path);
        break;
      case ClipType.curved:
        createCurve(size, path);
        break;
      case ClipType.arcUp:
        createBezierArcUp(size, path);
        break;
      case ClipType.triangle:
        createTriangle(size, path);
        break;
      case ClipType.wave:
        createWave(size, path);
        break;
    }
    path.close();
    return path;
  }

  createTriangle(Size size, Path path) {
    path.lineTo(size.width / 2, size.height);
    path.lineTo(size.width, 0.0);
  }

  createArc(Size size, Path path) {
    path.lineTo(0, size.height);
    path.arcToPoint(Offset(size.width, size.height), radius: Radius.elliptical(30, 10));
    path.lineTo(size.width, 0);
  }

  createBezierArcDown(Size size, Path path) {
    path.lineTo(0, size.height - 30);
    path.quadraticBezierTo(size.width / 2, size.height + 30, size.width, size.height - 30);
    path.lineTo(size.width, 0);
  }

  createBezierArcUp(Size size, Path path) {
    path.lineTo(0, size.height);
    path.quadraticBezierTo(size.width / 2, size.height - 60, size.width, size.height);
    path.lineTo(size.width, 0);
  }

  createCurve(Size size, Path path) {
    path.lineTo(0, size.height);
    var curXPos = 0.0;
    var curYPos = size.height;
    var increment = size.width / 20;
    while (curXPos < size.width) {
      curXPos += increment;
      path.arcToPoint(Offset(curXPos, curYPos), radius: Radius.circular(5));
    }
    path.lineTo(size.width, 0);
  }

  createPointedTriangle(Size size, Path path) {
    path.lineTo(0, size.height);
    var curXPos = 0.0;
    var curYPos = size.height;
    var increment = size.width / 40;
    while (curXPos < size.width) {
      curXPos += increment;
      curYPos = curYPos == size.height ? size.height - 30 : size.height;
      path.lineTo(curXPos, curYPos);
    }
    path.lineTo(size.width, 0);
  }

  createWave(Size size, Path path) {
    path.lineTo(0, size.height);
    path.quadraticBezierTo(size.width / 4, size.height - 40, size.width / 2, size.height - 20);
    path.quadraticBezierTo(3 / 4 * size.width, size.height, size.width, size.height - 30);
    path.lineTo(size.width, 0);
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}

And finally, an example page using all types of factory paths (screenshot at the start). We use Table with cells corresponding to those paths.

class CurvedBackgroundExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.lightBlueAccent,
        body: Center(
          child: Table(children: [
            TableRow(children: [
              buildCell(ClipType.arcDown),
              buildCell(ClipType.arcUp),
              buildCell(ClipType.wave),
            ]),
            TableRow(children: [
              buildCell(ClipType.pointed),
              buildCell(ClipType.triangle),
              buildCell(ClipType.curved),
            ])
          ]),
        ));
  }

  Widget buildCell(ClipType type) {
    return TableCell(
        child: Padding(
      padding: const EdgeInsets.all(8.0),
      child: TypedClipperWidget(
          clipType: type,
          child: Container(
            color: Colors.yellow,
            height: 120,
            child: Text(
              "${type.toString()}",
              textAlign: TextAlign.center,
            ),
          )),
    ));
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *