Water Drop effect in Flutter
In this post, I’d like to share with you how to achieve a water drop effect in Flutter. Why would you spend your time learning such skill? Well, I have no idea but it looks cool so let’s get started! 😀
TLDR: If you don’t care how it’s done and just wanna add water drops, use the water_drop package.
. . .
How it will work
The easiest to use solution would be to put an
OverlayEntry on top of everything, which would just alternate what’s beneath it. Unfortunately, to achieve this effect, we need to get a
child on which we are putting our water drops. Because of that, it will be much easier if we just use
Stack and put the drops on top of the child.
For each drop we are going to specify 4 main parameters:
height. They are defining the position and size of the drop relative to the
child, so if we want to have a 100px by 100px drop in the top left corner, we pass left and top as zeros, and height and width as 100s.
We also want to provide a Widget which will allow developers to easily add multiple drops (like in the video at the beginning of this article), so we will use a
Stack in which we will add
_WaterDrops for each
. . .
Cutting out the drop
At first, we need to take only the part of the widget we are going to use. To do that, we can use
ClipPath widget with our own
CustomClipper. It allows us to… well… cut out the portion of the widget. 😀 We will start with creating very simple
OvalClipper which will take care of defining what to cut out and what to leave:
To use it, we need to pass the
ClipPath widget just like in the code below. We also need to specify
Clip.hardEdge – this will prevent us from getting undesired artifacts later on. We put everything inside the
Stack as we will add more elements below and above the
ClipPath soon and we wrap everything in an
IgnorePointer so that the water drop doesn’t interfere with user’s gestures.
Right now we are not altering the cut out drop at all, so it’s not even noticeable that we do something, but if we remove the original child from the
Stack for a moment, we can see how our cut out looks like:
. . .
Why do we actually think that something is a water drop? Mostly because the light looks different there. So we need to add some minor changes which will imitate that in this place, something special is taking place. We will start by putting a
LinearGradient on top of our drop.
We’d like to have the gradient start in the top left corner with black color and smoothly change to white in the bottom right corner. At start, it seems rather easy, we just use
LinearGradient, set the begin to
Alignment.topLeft and the end to
Alignment.bottomRight and we’re good! Well, it’s not that simple. 🙁 If we wrap our child (even cut out) with the gradient, the
topLeft will be pointing to the top left corner of the whole widget (in our case a
Card), not the drop itself! If we want to have the gradient just over the drop, we need to calculate the alignment by ourselves.
How do we do it then? Well, we just need to implement the following steps:
- Get the size of the whole widget
- Get the center of the drop (based on
- Calculate the relative position of the center inside the whole widget
- Calculate the relative height and width inside the whole widget
- Subtract the relative height and width from the relative center to get the begin
- Add he relative height and width from the relative center to get the end
It may look like there’s a lot of work but the following code does it all:
Once we have the alignments, all we need to do is wrap our
BoxDecoration. What’s important here is that we need to set
BlendMode.overlay. This way our gradient will not just cover the water drop but it will “merge” with it and be much more subtle.
. . .
Light and shadow
Now let’s add a minor light reflection above the drop and shadow beneath it. Since we know the
width of the drop, we can just use
Positioned to specify the position and size of both the shadow and the light. Putting them inside the
Stack will also allow us to have the shadow under the drop and the light on top of it.
Now we only need to put them inside the
And now it will look like this:
. . .
Now it’s time to create a distortion effect where the view inside a bubble is a bit bigger and where it kinda bends on the edges of the drop. Surprisingly, it is very simple to achieve such a result.
What we can do, is put a few layers on top of each other. Each layer of the drop will be a bit smaller (the radius of the cutout will be smaller) but the content inside will be a bigger (we will scale what’s inside). If we have a several layers like that, it will create a zooming effect. If those layers will be different by a small factor, the whole effect will look very smooth. The code for this is very simple. All we have to do is generate around 8
ClipPaths, each having smaller
height. Then we wrap each of them in
Transform.scale to create zooming effect:
What’s worth noting here is that we are having 8 extra builds for each drop. It can cause performance problems so please think twice before using it! Especially if you want to wrap the whole page in it!
. . .
And that’s it!
Now we can add more
WaterDrops and even bind the parameters with the
AnimationController to make them move:
Founder of Fidev
Flutter enthusiast since Alpha release in 2017. Believes that sharing is caring, which lead him to start a technical blog dedicated fo Flutter in its early days. Loves to see beautiful designs become real apps and is willing to help make it happen. Enjoys sunny beaches far from home.
Join the newsletter!
Join the newsletter to keep track with latest posts and get my special Widget to animate views' entrances without any hassle for FREE!
Check out other posts!
Have you ever struggled to integrate card payments into your mobile Flutter app? If so, today is your lucky day! In this post, I present how to use Stripe Checkout in the Flutter app without any hassle!
Have you ever developed a widget or a page and you wanted to make sure it works correctly in different scenarios but then it turned out that you can’t just reproduce all the cases you want to cover? Framy may solve such problems!