Flutter: Animated Gradients via Custom Hooks

πŸ’‘
The code and method below has been updated to latest flutter as of Jan 15th, 2023.

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!

πŸ’‘
Full source code is available at: https://github.com/paddo/flutter_animated_gradient

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.

πŸ’‘
The below code has changed, and is simplified compared to the original article in 2019.

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!

May induce seizures

Source code is available at https://github.com/paddo/flutter_animated_gradient