Skip to content

flutter_animation_basics

Animations enhance user experience by providing visual feedback, guiding focus, and creating a more dynamic and engaging interface. Flutter provides a robust and flexible animation system that allows for both simple pre-canned animations and complex custom animations.

Flutter’s animation system is built around these core ideas:

  • Animation: The central class representing an animation value of type T (commonly double, but can be Color, Size, etc.). It knows the current value of the animation but not the state of the UI or what’s being animated. Its value can change over time.
  • AnimationController: Manages the animation playback. It’s a special Animation<double> that generates values from 0.0 to 1.0 over a specified duration. It lets you start (forward()), stop (stop()), reverse (reverse()), and track the animation’s status (AnimationStatus: dismissed, forward, reverse, completed). Requires a TickerProvider (vsync).
  • TickerProvider (vsync): Provides Ticker objects. Tickers signal when a new frame should be rendered, synchronizing the animation with the screen refresh rate. State objects of StatefulWidgets can implement TickerProviderStateMixin to act as a vsync.
  • Tween: Defines the mapping between the AnimationController’s 0.0-1.0 range and the actual range of values needed for the UI property (e.g., size from 50 to 150, color from red to blue). It doesn’t store state; it just defines the interpolation begin and end values.
  • Curve: Modifies the rate of change of an animation, making it non-linear (e.g., easing in, bouncing out). Applied to an AnimationController using CurvedAnimation.

The general process for creating explicit animations (where you manage the controller) involves:

  1. Create an AnimationController: Usually within the initState of a StatefulWidget. Requires duration and vsync (provided by TickerProviderStateMixin).
  2. Create a Tween: Define the beginning and end values for the property you want to animate.
  3. Combine Controller and Tween: Use the Tween.animate() method, passing the AnimationController (or a CurvedAnimation wrapping the controller) to create an Animation<T> object. This object will produce the interpolated values.
  4. Listen for Value Changes: Add a listener to the Animation object using addListener(). Inside the listener, call setState({}) to trigger a rebuild of the widget whenever the animation value changes.
  5. Use the Animation Value: In the build method, use the animation.value to set the property of the widget you are animating (e.g., width: sizeAnimation.value, color: colorAnimation.value).
  6. Start the Animation: Call methods like controller.forward(), controller.repeat(), etc.
  7. Dispose the Controller: Crucially, dispose of the AnimationController in the State’s dispose method to prevent resource leaks.

Simplifying with AnimatedWidget and AnimatedBuilder

Section titled “Simplifying with AnimatedWidget and AnimatedBuilder”

Manually calling addListener and setState can be verbose. Flutter provides helper widgets:

  • AnimatedWidget: A base class for widgets that animate based on a given Animation. You extend AnimatedWidget, pass the Animation to its constructor’s super(listenable: animation), and directly use the animation.value in the build method without needing addListener or setState. Useful for creating reusable animation widgets.
  • AnimatedBuilder: A more general-purpose widget for building animations. It takes an animation and a builder function. The builder function receives the BuildContext and an optional child widget. AnimatedBuilder listens to the animation and calls the builder function on every frame tick. It’s efficient because it only rebuilds the part of the widget tree returned by the builder, not necessarily the entire widget containing the builder. It’s often preferred over AnimatedWidget for specific, non-reusable animations within a larger build method.

Example using AnimatedBuilder (Fade Animation)

Section titled “Example using AnimatedBuilder (Fade Animation)”
import 'package:flutter/material.dart';
class FadeLogo extends StatefulWidget {
const FadeLogo({Key? key}) : super(key: key);
@override
_FadeLogoState createState() => _FadeLogoState();
}
// Need TickerProviderStateMixin for vsync
class _FadeLogoState extends State<FadeLogo> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// Create a Tween for opacity (0.0 to 1.0)
final opacityTween = Tween<double>(begin: 0.0, end: 1.0);
// Option 1: Linear animation
// _opacityAnimation = opacityTween.animate(_controller);
// Option 2: Curved animation
_opacityAnimation = opacityTween.animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn)
);
// Start the animation
_controller.forward();
// Optional: Make it repeat
// _controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose(); // IMPORTANT: Dispose controller
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _opacityAnimation, // Listen to this animation
builder: (context, child) {
// This builder function runs on every animation tick
return Opacity(
opacity: _opacityAnimation.value, // Use the current animation value
child: child, // Pass the child widget through
);
},
child: const FlutterLogo(size: 150), // The widget being animated (passed to builder)
),
);
}
}
// --- Usage Example ---
// void main() => runApp(MaterialApp(home: Scaffold(body: FadeLogo())));

For simpler animations where you want to animate a property change over a duration, Flutter provides Implicitly Animated Widgets. These widgets automatically animate changes to their properties.

Examples include:

  • AnimatedContainer: Animates changes to its width, height, color, padding, margin, decoration, transform, etc.
  • AnimatedOpacity: Animates changes to its opacity.
  • AnimatedPositioned: Animates changes to its position within a Stack.
  • AnimatedPadding: Animates changes to its padding.
  • AnimatedTheme: Animates theme changes.
  • AnimatedDefaultTextStyle: Animates changes to the default text style.

You simply change the target property value within a setState call, and the widget handles the animation controller and tweening internally.

import 'package:flutter/material.dart';
import 'dart:math';
class AnimatedBox extends StatefulWidget {
const AnimatedBox({Key? key}) : super(key: key);
@override
_AnimatedBoxState createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> {
double _width = 100.0;
double _height = 100.0;
Color _color = Colors.blue;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8.0);
void _animateBox() {
setState(() {
final random = Random();
_width = random.nextDouble() * 200 + 50; // Random width 50-250
_height = random.nextDouble() * 200 + 50; // Random height 50-250
_color = Color.fromRGBO(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
1,
);
_borderRadius = BorderRadius.circular(random.nextDouble() * 50);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Implicit Animation')),
body: Center(
child: AnimatedContainer(
// Provide the properties that should be animated
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
// Specify the duration and curve for the animation
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn, // Example curve
child: Center(child: Text('Animate Me!', style: TextStyle(color: Colors.white))),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _animateBox,
child: const Icon(Icons.play_arrow),
),
);
}
}
// --- Usage Example ---
// void main() => runApp(MaterialApp(home: AnimatedBox()));

Implicit animations are great for simple, state-driven UI transitions without the boilerplate of explicit animation controllers.

  • Use Implicit Animations for straightforward property changes triggered by state updates.
  • Use Explicit Animations (with AnimatedBuilder or AnimatedWidget) for more complex sequences, repeatable animations, fine-grained control over playback, or when animating properties not covered by implicit widgets.

Flutter’s animation system is powerful. Explore the extensive documentation and examples on flutter.dev to learn about physics-based animations, staggered animations, hero animations (page transitions), and more.