Flutter: Animated Gradients via Custom Hooks
When faced with the task of coming up with a re-usable "Animated Gradient" widget, I faced one problem, a gradient isn't actually a widget - I can't go and make an AnimatedWidget that I can use inside a BoxDecoration in my UI somewhere.
What I wanted was an actual gradient that simply took care of its own state management... not a widget, stateful or otherwise.
Enter Hooks
Since discovering hooks for flutter, I've become a huge fan, I'm making heavy use of hooks in my architecture, as shown in this post, and I find them particularly useful for use-cases such the one I'm about to outline below.
A hook is a new kind of object that encapsulates behaviour, keeping the state management out of your widget hierarchy. Hooks must be used within the build method of a Hook Widget, or within another hook. Hooks; combined with HookWidget, are a drop-in replacement for StatefulWidget and either enhance or replace other forms of state management you might be using.
For animation, hooks are particularly useful because they replace a huge swath of boilerplate and the need to create multiple classes - with just a couple of lines of code.
For this example we are going to make our own custom hook that itself contains a bunch of other hooks. We will be able to use our hook anywhere we might use a gradient.
Tutorial time!
Fire up a terminal
Open up pubspec.yaml, and add the flutter_hooks dependency, as below:
Let's make a custom tween, and a custom hook
Since we are animating a gradient, we'll need to make a tween for it.
Custom hooks are pretty easy to make. You can create a Hook class and a corresponding HookState. However, when using hooks within hooks, it's best to just use a function.
Explainer time:
- Put simply, we have an animation on repeat, and based on where we are in that animation, we interpolate between 2 gradients, all of these interpolations join together (and cycle back to the start), to form one smooth sequence.
- Our hook implementation lives inside the "useAnimatedGradient" function.
- Our initialisation and tear-down code is in the useEffect hook. Here we attach a listener, which is responsible for selecting the pair of gradients that we are presently animating between, and the corresponding listener tear-down code. See useEffect docs.
- The useAnimationController hook takes care of all the boilerplate required for animations - the Ticker Mix-ins, initialisation code, disposal, etc.
- Since we do need some state information that survives between rebuilds (namely, the present position in the array of gradients), we use a useValueNotifier hook. We could've used useState (which forces a rebuild), but in our use-case, we don't have to.
Using the hook
If you want to go and use it in your own project, you can use this hook with a list of gradients to cycle between. Use it anywhere you might use a gradient. Good luck!
For the sake of completeness though, and to bring this short sample to a conclusion, I'm going to make a widget to house our animation, so that we can encapsulate the state (and rebuilds) into as small a surface area as possible within the widget hierarchy.
Make a new file: lib/animated_gradient_box.dart
Now, layout a bunch of these boxes, and let the psychedelic adventures commence.
Make lib/main.dart look like this:
Behold the amazing result!
Source code is available at https://github.com/paddo/flutter_animated_gradient