![Flutter Gooey Blobs with CustomPainter.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac03c_gooey-blobs_640fcfb3316f48f20b3ec6e2c92a9736_800.jpeg)
Have you ever seen these gooey blobs on the internet?
![A gif of two blobs merging with each other.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac060_final-version-after-adding-drag-controls_af11f69f8f93cff24333364b7206ce2f.gif)
They look kinda cool, right? Have you ever thought it would be really cool to create something like this in Flutter?
A few days ago, Twitter user @double__glitch demonstrated creating gooey blobs in Figma. Seeing his technique reminded me of drawing something on a canvas, which led to me thinking about CustomPainter, and I thought, “I can do that! Somehow...”
After an hour or so, I did manage to re-create it in Flutter! Here’s how:
Why CustomPainter?
As I said earlier, the technique shown by @double__glitch is like drawing on a canvas. CustomPainter serves exactly that purpose. It provides you with set of APIs ranging from drawing shapes on a canvas to adding layer composite effects, combining image filters, blend modes, and more.
![Six different paint effects.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac06a_image-1-copy-2_e4c9c3ffcbb62093af2749a537fe2d82.gif)
We should be able to do this in Flutter, too! If you aren’t aware, Flutter converts your widget layers into a set of canvas painting instructions as part of the rendering pipeline. Somewhere down the line, these instructions are rendered on the screen.
Before diving into it, here’s something that you’ll need to know to achieve this effect:
![Zelda meme that says, "It's dangerous to go alone! Take this: saveLayer."](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac043_zelda-take-this_9e1a0c1f57b5bf57d7dcfeab2bd6f949_800.jpeg)
saveLayer
saveLayer
allows you to create composite effects on canvas. Normally each canvas instruction is painted individually, thus applying composite effects on a group of shapes. With saveLayer
, you can group those shapes and apply an effect on those as one single layer on the canvas.
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), paint);
// drawing instructions here are grouped together.
canvas.restore();
saveLayer
takes the size of the layer and a Paint
object. Any canvas instructions called after calling saveLayer
will be grouped, and once you call canvas.restore
, that group will be flattened out into a layer on which the Paint
object’s image filters and blend modes will be applied.
Now, back to creating our gooey blobs!
Adding Blurred Circles
We’ll use the canvas.drawCircle()
to draw two circles on the canvas and add a blur filter to the layer using saveLayer
.
import 'dart:ui';
import 'package:flutter/material.dart';
class BlobsView extends StatefulWidget {
const BlobsView({super.key});
@override
State<BlobsView> createState() => _BlobsViewState();
}
class _BlobsViewState extends State<BlobsView> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomPaint(
painter: _BlobsPainter(),
size: MediaQuery.of(context).size,
),
),
);
}
}
class _BlobsPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final blackCirclesPainter = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
final blurLayerPaint = Paint()
..color = const Color(0xff808080)
..style = PaintingStyle.fill
..imageFilter = ImageFilter.blur(
sigmaX: 10,
sigmaY: 10,
tileMode: TileMode.decal,
);
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), blurLayerPaint);
canvas.drawCircle(Offset(size.width / 2 - 50, size.height / 2), 70, blackCirclesPainter);
canvas.drawCircle(Offset(size.width / 2 + 50, size.height / 2), 60, blackCirclesPainter);
canvas.restore();
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
Here’s how it looks. Nothing fancy right now.
![Two blurry conjoined circles.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac047_screenshot-2024-07-07-at-52848-pm_50bb5ef87dabe7fb5fa0d0ef6eb3e600_800.jpeg)
Adding a colorDodge Layer
Next, we’ll add certain layers on top of our canvas with specific blend mode applied to them. The blend mode will specify how the layer should be composed with its background. In this article, we won’t dive deep into how these blend modes work, but if you’re curious, I would recommend reading these w3 docs on the different blend modes mentioned in this article and how they work.
First is the colorDodge
layer. We’ll achieve this by creating a rectangle, giving it a bright color like Color(0xff808080)
, and setting its blend mode to BlendMode.colorDodge
.
This will go right after the canvas.restore
call:
canvas.drawRect(
Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height),
Paint()
..color = const Color(0xff808080)
..style = PaintingStyle.fill
..blendMode = BlendMode.colorDodge,
);
Now we get something interesting.
![Two conjoined circles that are only a little blurry.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac04e_screenshot-2024-07-07-at-52918-pm_96c5f146682b0df9f2db44e7a60b82b8_800.jpeg)
colorDoge
is one of the blend modes that can be used to brighten the destination image (our canvas in this case) with respect to to the source image (blend mode layer). It basically brightens the brighter areas and adds more contrast and saturation to them. In our case, it helps create sharper outlines for our circles, replacing the blurred outlines. Darker areas of the destination image aren’t affected as much as the brighter areas.
Adding a colorBurn Layer
Now we’ll add a colorBurn
layer on top of our whole canvas. Similar to what we did earlier, we’ll create a rectangle, make it black, and set its blend mode to BlendMode.colorBurn
.
canvas.drawRect(
Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height),
Paint()
..color = Colors.black
..style = PaintingStyle.fill
..blendMode = BlendMode.colorBurn,
);
We’re getting close!
![Two conjoined circles that are not blurry.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac040_screenshot-2024-07-07-at-52931-pm_b76dc0d63332c4144b1eda0d5feef982_800.jpeg)
colorBurn
is basically the opposite of colorDodge
. It darkens the destination image with respect to the source image. With this, the inside blurred areas of the circles become completely black, creating a sharp, gooey shape.
Adding A Gradient Layer
As before, we’ll add a gradient layer with a blend mode set to screen
. Setting the blend mode to screen
will compose this layer as an overlay on top of our gooey shapes, excluding the white areas of the canvas.
canvas.drawRect(
Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height),
Paint()
..style = PaintingStyle.fill
..shader = const RadialGradient(
colors: [Colors.yellow, Colors.pink],
).createShader(
Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: size.width,
height: size.height,
),
)
..blendMode = BlendMode.screen,
);
With that, our gooey blobs are ready! 🎉
![Two circles melting into each other.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac060_final-version-after-adding-drag-controls_af11f69f8f93cff24333364b7206ce2f.gif)
This is the final version after adding drag controls.
We finally have gooey blobs in Flutter and that wasn’t crazy hard to do. It’s interesting how using composite effects with various blend modes can help you create some interesting visuals like this.
Use these blobs cautiously though :)
![The evil ghost blob from Ghostbusters.](https://cdn.prod.website-files.com/6143afec68f555387049efb3/669047b478843233a55ac065_image-2_fd2826d41c52306daa91bca3c7a21719.gif)
Conclusion
You can find the source code for these gooey blobs here among some of my other creative work. Feel free to poke around and play with them :)