This is a flutter app that utilizies its animation and painting API to make a very modern, smooth, animation. This was a real challenge to build, specially because it was my first time learning how to use flutter's CustomPainter.
Big thanks to Tubik @dribble for making this awesome design and inspiring me to do this app!
An overview of how the code works
This is a pretty advanced app, so I'll assume you know the basics of flutter.
The first thing I want to talk about is the
HeartBeatAppBarContent widget. As you can see, it's a
StatefulWidget inside of the
HeartBeatAppBar widget, and its state uses the
SingleTickerProviderStateMixin. For those of you who don't know, this is what allows us to animate widgets in flutter. We have one
AnimationController and 3
The controller itself is what's going to control the animation values
_activatingOpacitywill animate the opacity of one of the icons so it becomes brighter (indicating it being activated)
_deactivatingOpacitywill animate the opacity of one of the icons so it becomes dimmer (indicating it being deactivated)
_heartBeatAnimationis what's going to allow us to control the percentage of the animationg, defining the beginning and ending points of the path of the Painter, so we actually see the line of the heartbeat moving.
We have 3 properties in the
_activatedallows us to keep track of what is the activated icon so that, when we click one of them, we can animate from the _activated to the
_nextActivatedallows us to keep track of what's the next icon that is going to be activated so that we can make the heartbeat path from the
_activatedX position, to the
_nextActivatedX position. Its default value is 0, and its immediately changed when the animation is called
_positionsis a List of doubles which allows us to know what is the position in the X axis of each icon. This was hardcoded but it can and should be responsive to the width of the device.
When an animation is called,
_nextActivatedgets the value of the clicked button (if the first icon was clicked, then
_nextActivatedis set to 1, since the default value is 0). Now, we give the HeartBeatPainter its
_positions[_nextActivated].In other words, the
HeartBeatPathwill start at the position of the activated icon, and will go to the position of the next activated icon. Once the animation ends,
_activatedis set to
_nextActivatedand _nextActivated is set back to 0.
This is where things get a little hairy, but I'll try to keep it very simple.
HeartBeatPainter extends CustomPainter. That means that it needs two methods:
shouldRepaint. Since we're using an animation coming from its parent widget, we are not going to need
shouldRepaint, so it just returns false.
We have 3 properties:
beginningis the starting X position of the
HeartBeatPath, and its coming from
endingis the ending X position of the
HeartBeatPath, and its coming from
Animation<double>which is what allows us to know how much of the animation has passed. So if its value is 0.5, that means 50% of the animation has gone through, and that's useful to determine what is the current state of the path itself. This will be passed to the
superconstructor as the
Then we are defining 3 functions.
parabola, which are used to determine some of the values. I'm not going to go through the math of this, but this allows us to have smoother animations.
We also have a
bool inversed a
List<Offset> _points. The first one is important to determine if the path will have to go from left to right, or from right to left. The second will contain all of the points that are going to be used to make our heartbeat path. That means
_points is the first point of the path, and the last value of
_points is last point of the path.
_points, we hard coded a bunch of values:
- The ones that start with
beginning + a * PERCENTAGEallows us to make a lot of points in between the beginning of the path, and the heartbeat wave (which is always in the middle). If we didn't have this, the path would be "skippy", and the animation would not look smooth at all.
- The same goes for the ones that start with
startingPoint + 30 + b * PERCENTAGE. This makes a bunch of points in between the ending of the heartbeat wave, and the ending point of the whole path.
- All of the other points are the heartbeat wave being hard coded
startingPointis the X position of the beginning of the heartbeat wave.
As you can see from the code,
_positionschanges depending on the
_inversedvalue, and therefore, on the direction of the heartbeat animation.
Now here comes the main part. We have a variable
start that indicates what is the first point of our path, and this changes depending on the
_animation.value. Hence, if
_animation.value is closer to 1, the
start value will be greater, which makes the back of the path start to move when the animation starts progressing. We also have a
percentage variable, which is what controls the front of the path, since this will define the last point of our path.
From there, we are calling a for loop, that goes from the
start point (first), to
percentage point (last), and adds all of the points in between to the path.
The color of the path (which is a gradient) and the circle (which is the indicator of the current icon) also animate and get its properties depending on the