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”:
- AnimatedAlign
- AnimatedContainer
- AnimatedDefaultTextStyle
- AnimatedOpacity
- AnimatedPadding
- AnimatedPhysicalModel
- AnimatedPositioned
- AnimatedPositionedDirectional
- AnimatedTheme
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