In this blog, we talk about how flutter is smart enough in making good use of the obtained elapsed time from Ticker API.
In Flutter’s Ticker API, we achieved the first step of getting the latest value of elapsed time whenever it changes, through Flutter’s Ticker API.
Flutter offers a Curve abstract class which comes with transform(double t) which is the curve function representing the curve class. The transform method is used to get different values of the curve at different provided values of t.
Flutter sets some restrictions on the transform method to
Through these restrictions, flutter is trying to communicate that the curves which are being represented through the subclasses of Curve will always have mappings of t from 0.0 to 1.0 to return values within the range of 0.0 to 1.0. If you consider the below empty graph, the rectangular region is where the graph of any transform function would lie.
There are many in-built curves supported by flutter out of the box, making it easy to use some of the most commonly used curves like Curves.bounceIn, Curves.bounceOut, Curves.easeIn, and many more.
To have a look at the complete list of curves supported by Flutter, you can visit Curves classes by Flutter.
After getting some idea about what Flutter has to offer regarding curve functions, we can finally talk about the last step which we need to achieve the animation using Flutter.
By the end of the previous part of Flutter’s Ticker API, we discussed the ticker callback provided by Flutter’s Ticker API which gets called with a fresh new instance of elapsed time, after every new frame is being rendered by the underlying rendering engine of flutter.
But we found from the above Curves section that every curve function offered by Flutter has some restrictions regarding the domain and range of the function, which will have to obey to make use of curve functions.
Here, when Curve.transform(t) method restricts the allowed values of t from 0.0 to 1.0, it wants to convey that it expects the percentage of time elapsed, instead of the actual time elapsed in duration.
Thus, here we would want to convert the elapsed time obtained from the ticker’s callback into the percentage of time elapsed concerning the total desired duration of the animation.
Thus, if we want the duration of an animation to be of around Duration(seconds: 5), and for instance, the ticker’s callback is invoked with an elapsed time of around Duration(seconds: 2), the Curves.transform(t) method expects the percentage of time elapsed which would be elapsedDurationInSeconds / totalDurationInSeconds.
Now, we will get a new value inside the ticker’s callback, at every change in elapsed time. The value stored inside the variable value will always be between 0.0 to 1.0 inclusive.
Our next goal should be to use the obtained value to obtain the corresponding value of the widget’s property.
Consider a widget that is a simple Container with color: Colors.black, and we would want to animate its height property from 0px to 100px over 5 seconds.
Here, we are using the range of values of the curve and converting it to the range of heights of the container. i.e. We want the height of the container to change from 0 to 100px, thus if we multiply 100.0 with every changing value of the curve, the value of the height will range from 0 to 100.0 because the value of the curve will always range from 0.0 to 1.0.
We can use this strategy to change any widget’s property over a range of values. For instance, if we want to rotate a widget by an angle of pi/2, we would want to multiply pi/2 with every changing value of the curve.
Thus, we finally achieved the last step to create animations with flutter, which was using the elapsed time obtained inside the ticker’s callback on every new frame, and using the elapsed time to compute the next curve’s value and in turn it to determine the current value of widget’s property.
Although this might seem good enough to go with, flutter has a set of dedicated helper classes just to achieve the same as we tried to in the above implementation. These set of animation helpers combined are referred to as Animation API. Let’s have a look at it next.
In the above section, we discussed how to use Ticker’s API along with Curve to implement the desired curved animation we would want to update the value of a widget’s property.
Flutter offers many helper abstractions which pretty much achieve the same thing which we did in the initializeAnimation method in the above section. These helper abstractions together are referred to as Flutter’s Animation API.
Flutter exposes AnimationController which internally sets up the Ticker similar to the way we did above.
It has several other helper methods which are dedicated to helping easily achieve more control over changes in values of curves with changes in elapsed time obtained from the ticker’s callback. Some of the most commonly used methods are:
To instantiate AnimationController, it has a default constructor which takes in a required parameter vsync which is of type TickerProvider. To achieve this, we would need to mix in the state class of the widget with either of SingleTickerProviderStateMixin or TickerProviderStateMixin, which turns the state class of the widget into a class that has the capability of a provider ticker.
Let’s replace the implementation of animation using AnimationController:
One thing we missed is letting the Animation’s API know about which curve we would want to use. By default, AnimationController uses Curves.linear curve. Flutter’s Animation API offers a special class just for this use case, the CurvedAnimation, which is very well designed to decouple the dependency of the curve from AnimationController from which curve to be used.
CurvedAnimation also exposes similar methods as of AnimationController. It has a default constructor which takes in a required parameter of the parent of type AnimationController along with the parameter curve of type Curve.
CurveAnimation’s sole purpose is to convert the value of the AnimationController to the value which corresponds to the curve for that particular value of elapsed time.
Thus, mostly all of the code will be the same, just we would want to update curveValue with the value obtained from CurvedAnimation.
If you have been working with Flutter for quite a while now, you might have seen something like this already, but now after having to understand all of the behind-the-scenes stuff that goes under the hood, this should not seem a mystery anymore.
It is quite normal if you are feeling something like this right now if you are new to flutter. Every Flutter dev has been through this phase at least once!
One more thing which was quite bothering to Flutter people was this very pattern of keeping track of updated curveValue. This pattern was so common to be able to achieve curved animations in a flutter that they have a dedicated widget AnimatedBuilder useful to get rid of maintaining yet another state value just for keeping track of updated curveValue.
AnimatedBuilder is a widget that takes in the instance of AnimationController or CurvedAnimation to listen for and gives a builder callback, which gets called whenever there’s a new value of the animation.
Thus, we need not maintain a subscription on an instance of either AnimationController or CurvedAnimation just to set the new value of curvedValue. We could directly refer .value property of either AnimationController or CurvedAnimation from inside the widget. Which eliminates the need for maintaining the state curvedValue.
Yeah, flutter always knows what you want in the end! and yeah, flutter is not done yet!
If this was not all, flutter found this pattern to be very common as well to change container’s properties through a curved animation over a particular duration. Thus, flutter offers AnimatedContainer widget to achieve the same and even more than that. It is an animated version of Container that gradually changes its values over some time.
The AnimatedContainer will automatically animate between the old and new values of properties when they change using the provided curve and duration. Null properties are not animated. Its child and descendants are not animated. This class is useful for generating simple implicit transitions between different parameters to Container with its internal AnimationController.
There are also many widgets offered by Flutter which are wrappers around several common animation patterns. You can have a look at all of them here.
The core basics behind all these widgets are what we have been discussing in this series of articles. All these widgets use the Ticker API under the hood along with their own set of optimizations. From here on, you must be able to imagine what actually happens behind the scenes and thus readily get used to using the abstractions offered by Flutter.
This brings us to conclude this 3 part series of articles on diving deep into animations in a flutter.
- elapsed time: how can we keep on getting the fresh instance of elapsed time as and when elapsed time changes, since the animation has started?
- curve: Which curve to use to compute the next value of the widget’s property(height) given an elapsed time.
I hope this 3 part series helped connect the dots well to get a deeper understanding of what happens under the hood.