20

How do I "cut a hole" in a shape with flutter canvas? I have this rather complex set of shapes that is made to look like a real world object. This object has a hole in it shaped like a rounded rectangle.

I would really like to subtract a RRect from a shape, but I cannot find any information on how to do this. canvas.clipRRect(myRRect) just removes everything that is not covered by myRRect. I want the opposite of that. i.e. to make a myRRect shape hole in the current canvas shape or shapes.

Yann39
  • 14,285
  • 11
  • 56
  • 84
Mathias Nielsen
  • 1,560
  • 1
  • 17
  • 31

4 Answers4

33

You can use Path.combine along with the difference operation to create the hole.

The custom painter :

class HolePainter 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.circular(10))),
          Path()
            ..addOval(Rect.fromCircle(center: Offset(200, 200), radius: 50))
            ..close(),
        ),
        paint,
    );
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return null;
  }
  
}

Usage :

class EditAvatar extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('Hole in rounded rectangle'),
      ),
      body: CustomPaint(
        painter: HolePainter(),
        child: Container(),
      ),

  }

}

Result :

enter image description here

Of course if you want the hole to be a rounded rect simply substract an RRect instead of a Circle.

Nils Reichardt
  • 3,195
  • 2
  • 18
  • 28
Yann39
  • 14,285
  • 11
  • 56
  • 84
16

A solution is to use PathFillType.evenOdd:

// circle with empty RRect inside
final Path path = Path();
path.fillType = PathFillType.evenOdd;
path.addOval(Rect.fromCircle(center: center, radius: radius));
path.addRRect(RRect.fromRectAndRadius(
    Rect.fromCircle(center: center, radius: radius / 2),
    Radius.circular(radius / 10)));
canvas.drawPath(path, paint);

enter image description here

monsieurtanuki
  • 191
  • 1
  • 6
3

You can try with different BlendMode in Custom Painter, Below is one of the example which you can refer:

class MyPaint extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // below one is big circle and instead of this circle you can draw your shape here.
    canvas.drawCircle(Offset(200, 200), 100, Paint()
      ..color = Colors.orange[200]
      ..style = PaintingStyle.fill);

    // below the circle which you want to create a cropping part.
    RRect rRect = RRect.fromRectAndRadius(Rect.fromCenter(center: Offset(200, 200), width: 75, height: 75), Radius.circular(8));
    canvas.drawRRect(rRect, Paint()
      ..color = Colors.orange[200]
      ..style = PaintingStyle.fill
      ..blendMode = BlendMode.dstOut);

    canvas.save();
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

Here, I have used BlendMode.dstOut which will be used to show the destination sources, but only where the two sources do not overlap.

Jigar
  • 421
  • 2
  • 11
  • 2
    Thanks for the suggestion. Sadly I could not find any blendmode that did what I wanted. Some of them draw the last shape as a black on top, but most seem to do nothing. – Mathias Nielsen Jul 08 '19 at 22:00
0

for anybody that can't get Yann39's solution working. For me the problem was that i didnt use canvas.save() at the end of the paint method

JJan1999
  • 1
  • 1