ImplicitlyAnimatedWidget – The simple way to animate Widgets

It can be a little bit rough to learn , how animations work in Flutter projects. Fortunately Flutter provides some very handy widgets which are animated out of the box when a property is changed which is called “implicit animation”:

However, it would be cool to create custom widgets with this capabilities.  All the widgets listed above are implementing the ImplicitlyAnimatedWidget class, so I tried to do the same: After some struggle I found a  codesnippet on Stackoverflow which helped a lot.

My goal was to create an implicitily animated FloatingActionButton where thebackgroundColor is changed smoothly when a new color is assigned:

import 'package:flutter/material.dart';

class AnimatedFab extends ImplicitlyAnimatedWidget {
  final Color foregroundColor;
  final Color backgroundColor;
  final Function onPressed;
  final Widget child;
  final String tooltip;

  AnimatedFab({
    Key key,
    @required this.onPressed,
    this.foregroundColor = Colors.white,
    this.tooltip = '',
    @required this.backgroundColor,
    @required Duration duration,
    this.child,
    Curve curve = Curves.linear
  }) : super(duration: duration, curve: curve, key: key);

  @override
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() => _AnimatedFabState();
}

class _AnimatedFabState extends AnimatedWidgetBaseState<AnimatedFab> {

  ColorTween _colorTween;

  @override
  void forEachTween(TweenVisitor visitor) {
    _colorTween = visitor(_colorTween, widget.backgroundColor, (dynamic value) => ColorTween(begin: value));
  }

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: widget.onPressed,
      tooltip: widget.tooltip,
      backgroundColor: _colorTween.evaluate(animation),
      foregroundColor: widget.foregroundColor,
      child: widget.child,
    );
  }


}

Description

The AnimatedFab class ist a stateful widget which builds a FloatingActionButton. The constructor requires a Duration object to finetune the length of the duration. I set the animation curve to Curves.linear because this fits best for color animations. For other animations you might want to expose this parameter in the constructor as well. Duration and curve are used in the call of the superclass super(duration: duration, curve: curve, key: key).

The magic happens in the state part class _AnimatedFabState extends AnimatedWidgetBaseState<AnimatedFab>.

The backgroundColor propery of the FloatingActionButton is not a Color but a ColorTween which is basically a color transformation which can be animated. In addition to the build method we overrite a forEachTween method. This method is automatically called right before the build method and has access to the old properties. In our case the _colorTween gets recreated by a TweenVisitor. The TweenVisitor is called with three arguments, the first being the current value of the Tween<Color> (initially null). The second argument is the target value of the tween (the new backgroundColor), and the third being a callback that takes a value (the backgroundColor in our case), and that returns an Tween object.

This new Tween is now assigned to the backgroundColor in the build method and called with the evaluate contructor: backgroundColor: _colorTween.evaluate(animation),. The animation  object is automatically provided by the AnimatedWidgetBaseState.

Sounds complicated, but its actually pretty short and easy to implement.

Usage

You can use this AnimatedFab like the vanilla FloatingActionButton:


import 'dart:math';

...

int colorIndex = 0;
Color _fabColor = Colors.red;

_onFabPressed() {
  var random = Random();
  _fabColor = Colors.accents[random.nextInt(Colors.accents.length - 1)];
  setState(() {});
}

Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimatedFAB"),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      floatingActionButton: AnimatedFab(
        onPressed: _onFabPressed,
        duration: Duration(milliseconds: 250),
        backgroundColor: _fabColor,
        foregroundColor: Colors.white,
        child: Icon(Icons.play_arrow),
      ), 
    );
  }
}

I love this approach, because it encapsulates widgets with its animations. This keeps the the page code short and clean. And you can reuse the animated widget even in other projects.

 

EDIT 03.10.2019 – Typo in code removed

Leave a Comment

*