flutter
/

Flutter Animations – Implicit and Explicit Animations

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter Animations – Implicit and Explicit Animations

Why Animations in Flutter?

Animations bring your app to life. They provide visual feedback, guide user attention, and create a polished user experience. Flutter offers two main approaches to animations: implicit animations (easy, for simple property changes) and explicit animations (full control over animation curves, timing, and composition). This guide covers both, giving you the tools to create everything from subtle fades to complex choreographed sequences.

Implicit Animations (Simple, Automatic)

Implicit animations are widgets that automatically animate when you change their properties. They are perfect for simple transitions like fading, scaling, rotating, or changing color/size. Common implicit widgets: AnimatedContainer, AnimatedOpacity, AnimatedPadding, AnimatedAlign, AnimatedPositioned, AnimatedDefaultTextStyle.

DARTRead-only
1
class ImplicitAnimationDemo extends StatefulWidget {
  @override
  _ImplicitAnimationDemoState createState() => _ImplicitAnimationDemoState();
}

class _ImplicitAnimationDemoState extends State<ImplicitAnimationDemo> {
  bool _selected = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _selected = !_selected),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        width: _selected ? 200 : 100,
        height: _selected ? 200 : 100,
        color: _selected ? Colors.blue : Colors.red,
        child: Center(
          child: Text('Tap me', style: TextStyle(color: Colors.white)),
        ),
      ),
    );
  }
}

In this example, tapping toggles a boolean, and the AnimatedContainer animates between sizes and colors over 300ms.

Explicit Animations (Full Control)

Explicit animations give you complete control over the animation lifecycle. You use an AnimationController to drive the animation, Tween to define the range of values, and an AnimatedBuilder or AnimatedWidget to rebuild the widget with the current animation value. This pattern is ideal for complex, continuous, or looping animations.

AnimationController

The AnimationController produces a value from 0.0 to 1.0 over a specified duration. It can be started, stopped, repeated, or reversed. You must dispose it in dispose().

DARTRead-only
1
late AnimationController _controller;

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    vsync: this, // required: provide a TickerProvider (like StatefulWidget)
    duration: Duration(seconds: 2),
  );
}

@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

void _startAnimation() {
  _controller.forward(); // or .repeat(), .reverse(), .stop()
}

Tween

A Tween maps the controller's 0.0–1.0 range to a different range of values. For example, to animate between two colors, use ColorTween. To animate position, use Tween<Offset> or Tween<double> for scale, opacity, etc.

DARTRead-only
1
final colorTween = ColorTween(begin: Colors.red, end: Colors.blue);
final sizeTween = Tween<double>(begin: 50, end: 200);

// Access current value inside an animation builder
Color currentColor = colorTween.evaluate(_controller);
double currentSize = sizeTween.evaluate(_controller);

AnimatedBuilder – Efficient Rebuilding

AnimatedBuilder listens to an animation and rebuilds only the parts that depend on it. It's more efficient than a full widget rebuild. You provide a builder that returns the widget tree, and an optional child that won't rebuild (performance optimization).

DARTRead-only
1
AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    return Transform.scale(
      scale: sizeTween.evaluate(_controller),
      child: Container(
        color: colorTween.evaluate(_controller),
        width: 100,
        height: 100,
        child: child,
      ),
    );
  },
  child: const Text('I don\'t rebuild'),
)

AnimatedWidget – Reusable Animated Widgets

AnimatedWidget is a base class for creating widgets that listen to an animation and rebuild when it changes. It's a cleaner alternative to AnimatedBuilder when you have a single animation to listen to and want to encapsulate the logic.

DARTRead-only
1
class FadeInWidget extends AnimatedWidget {
  final Widget child;

  FadeInWidget({required Animation<double> animation, required this.child})
      : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Opacity(
      opacity: animation.value,
      child: child,
    );
  }
}

// Usage
FadeInWidget(
  animation: _controller,
  child: Text('Fading in'),
)

Common Animation Patterns

    • Fade transition: Use AnimatedOpacity or a Tween<double> with Opacity widget.
    • Size transition: Use AnimatedContainer or Tween<double> with Transform.scale.
    • Position transition: Use AnimatedPositioned (inside a Stack) or SlideTransition.
    • Rotation: Use AnimatedBuilder with Transform.rotate and a Tween<double>.
    • Color transition: Use AnimatedContainer or ColorTween with AnimatedBuilder.

Curves and Intervals

Curves control the rate of change of an animation. Flutter provides many built‑in curves: Curves.linear, Curves.easeIn, Curves.easeOut, Curves.bounceIn, etc. You can also create custom curves. Apply a curve by passing it to the AnimationController or by using CurvedAnimation.

DARTRead-only
1
final curvedAnimation = CurvedAnimation(
  parent: _controller,
  curve: Curves.easeInOutBack,
);

Staggered Animations

Staggered animations sequence multiple animations using a single AnimationController. You can use Interval to delay or overlap parts of the animation. For example, you might fade in one element, then slide another, then rotate a third.

DARTRead-only
1
final opacityAnimation = Tween<double>(begin: 0, end: 1).animate(
  CurvedAnimation(
    parent: _controller,
    curve: Interval(0.0, 0.5, curve: Curves.easeOut),
  ),
);
final scaleAnimation = Tween<double>(begin: 0, end: 1).animate(
  CurvedAnimation(
    parent: _controller,
    curve: Interval(0.3, 1.0, curve: Curves.elasticOut),
  ),
);

Key Takeaways

    • Implicit animations (e.g., AnimatedContainer) are simple and automatic for property changes.
    • Explicit animations use AnimationController, Tween, and AnimatedBuilder for full control.
    • Always dispose of AnimationController in dispose().
    • Use CurvedAnimation to apply easing curves.
    • For multiple animations, use staggered intervals with a single controller.
    • AnimatedWidget is great for reusable animated components.

Try it yourself

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Animation Demo',
      home: const AnimatedBox(),
    );
  }
}

class AnimatedBox extends StatefulWidget {
  const AnimatedBox({super.key});

  @override
  _AnimatedBoxState createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _sizeAnimation;
  late Animation<Color?> _colorAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );

    _sizeAnimation = Tween<double>(begin: 50, end: 150).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );

    _colorAnimation = ColorTween(begin: Colors.blue, end: Colors.red).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _toggleAnimation() {
    if (_controller.isCompleted) {
      _controller.reverse();
    } else {
      _controller.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Animated Box')),
      body: Center(
        child: GestureDetector(
          onTap: _toggleAnimation,
          child: AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Container(
                width: _sizeAnimation.value,
                height: _sizeAnimation.value,
                color: _colorAnimation.value,
                child: const Center(
                  child: Text('Tap me', style: TextStyle(color: Colors.white)),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

Which widget is best for animating a color change without writing a controller?

A
AnimatedContainer
B
AnimatedBuilder
C
AnimatedColor
D
ColorTween
Q2
of 4

What is the role of `AnimationController` in explicit animations?

A
It defines the range of values to animate between
B
It drives the animation by producing values from 0 to 1 over time
C
It applies a curve to the animation
D
It disposes the animation
Q3
of 4

What method should you call to release an `AnimationController`?

A
release()
B
dispose()
C
close()
D
stop()
Q4
of 4

Which widget rebuilds only the parts that depend on the animation?

A
AnimatedBuilder
B
AnimatedWidget
C
AnimatedContainer
D
AnimationController

Frequently Asked Questions

When should I use implicit vs explicit animations?

Use implicit animations when you want to animate a single property change in response to a state change. They are easy and require minimal code. Use explicit animations when you need more control, such as looping, reversing, or synchronising multiple animations, or when you want to animate continuously without a state change (e.g., a loading spinner).

What is `TickerProvider` and why do I need it for `AnimationController`?

AnimationController needs a TickerProvider to provide a Ticker that drives the animation. In a StatefulWidget, you can use this as the vsync by mixing in SingleTickerProviderStateMixin (for one controller) or TickerProviderStateMixin (for multiple).

How do I animate a custom widget property?

Use an AnimatedBuilder or AnimatedWidget that reads an animation value and applies it to your widget's property (e.g., Transform.rotate for rotation). You can also create a custom ImplicitlyAnimatedWidget by extending it, but that's advanced.

Can I use `setState` with animations?

Yes, but it's inefficient. The recommended way is to use AnimatedBuilder which only rebuilds the parts that depend on the animation. Using setState would rebuild the whole widget tree each frame, causing performance issues.

How do I make an animation repeat forever?

Call _controller.repeat() after initialising the controller. You can also set _controller.repeat(reverse: true) to ping‑pong.

Previous

flutter stream

Next

flutter hero animation

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.