Creating Smooth Animations with AnimationController

Creating Smooth Animations with AnimationController

What You’ll Learn

Master Flutter’s AnimationController to create custom, buttery-smooth animations. This is the foundation for all animations in Flutter—from simple fades to complex transitions.

Why AnimationController?

Flutter’s implicit animations (like AnimatedContainer) are great for simple cases, but AnimationController gives you full control over timing, curves, and sequencing. It’s essential for building polished, professional UIs.

Example: Animated Logo with Fade and Scale

import 'package:flutter/material.dart';

class AnimatedLogo extends StatefulWidget {
  @override
  State<AnimatedLogo> createState() => _AnimatedLogoState();
}

class _AnimatedLogoState extends State<AnimatedLogo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _fadeAnimation;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    super.initState();

    // Create the controller
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this, // Prevents offscreen animations
    );

    // Fade from 0 to 1
    _fadeAnimation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.easeIn,
      ),
    );

    // Scale from 0.5 to 1.0
    _scaleAnimation = Tween<double>(
      begin: 0.5,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.elasticOut,
      ),
    );

    // Start the animation
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose(); // Always clean up!
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Animation Demo')),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Opacity(
              opacity: _fadeAnimation.value,
              child: Transform.scale(
                scale: _scaleAnimation.value,
                child: child,
              ),
            );
          },
          child: FlutterLogo(size: 200),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Toggle animation direction
          if (_controller.isCompleted) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}

Key Concepts Explained

1. SingleTickerProviderStateMixin

2. Tween

3. CurvedAnimation

4. AnimatedBuilder

Animation Control Methods

_controller.forward();       // Start animation
_controller.reverse();       // Play backward
_controller.repeat();        // Loop forever
_controller.reset();         // Back to start
_controller.stop();          // Pause where it is
_controller.animateTo(0.5);  // Go to specific value

Advanced: Sequential Animations

Animate multiple properties in sequence:

void initState() {
  super.initState();
  _controller = AnimationController(
    duration: Duration(seconds: 3),
    vsync: this,
  );

  // First half: fade in (0.0 to 0.5)
  _fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
    CurvedAnimation(
      parent: _controller,
      curve: Interval(0.0, 0.5, curve: Curves.easeIn),
    ),
  );

  // Second half: scale (0.5 to 1.0)
  _scaleAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
    CurvedAnimation(
      parent: _controller,
      curve: Interval(0.5, 1.0, curve: Curves.bounceOut),
    ),
  );

  _controller.forward();
}

The Interval curve splits your animation timeline into segments!

Try It Yourself

Build a loading spinner with these features:

  1. Rotate continuously using _controller.repeat()
  2. Add a RotationTransition widget
  3. Use Tween<double>(begin: 0, end: 1) for a full rotation
  4. Combine with a pulsing scale animation

Challenge: Create a “like” button that scales up and changes color when tapped, then bounces back. Use ColorTween and combine it with your scale animation.

Tip of the Day

Performance: Use AnimatedBuilder instead of setState() for animations. AnimatedBuilder only rebuilds the animated widget, while setState() rebuilds the entire widget tree.

Debugging: Add a listener to see animation values in real-time:

_controller.addListener(() {
  print('Animation value: ${_controller.value}');
});

Use Flutter DevTools’ animation inspector to visualize and slow down animations for debugging.