DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter Animations: AnimationController, Tween, and Hero Explained

Flutter Animations: AnimationController, Tween, and Hero Explained

Flutter animations become simple once you understand the mental model. Three patterns cover 90% of what you'll ever need.

The Core Concepts

AnimationController: manages the timeline (0.0 → 1.0)
Tween:              interpolates values (start → end)
Animation:          the combination of both
Enter fullscreen mode Exit fullscreen mode
class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
Enter fullscreen mode Exit fullscreen mode

SingleTickerProviderStateMixin provides vsync. Use TickerProviderStateMixin for multiple simultaneous animations.

Pattern 1: Fade In

FadeTransition(
  opacity: _animation,
  child: const Text('Hello, Flutter!'),
)

ElevatedButton(
  onPressed: () => _controller.forward(),
  child: const Text('Show'),
)
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Slide In

late final Animation<Offset> _slideAnimation;

@override
void initState() {
  super.initState();
  _controller = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 400),
  );
  _slideAnimation = Tween<Offset>(
    begin: const Offset(0, 1),  // slide from bottom
    end: Offset.zero,
  ).animate(CurvedAnimation(
    parent: _controller,
    curve: Curves.easeOutCubic,
  ));
}

SlideTransition(
  position: _slideAnimation,
  child: Container(
    padding: const EdgeInsets.all(16),
    color: Colors.blue,
    child: const Text('Slide In'),
  ),
)
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Hero Animation (Screen Transitions)

// List page
GestureDetector(
  onTap: () => Navigator.push(
    context,
    MaterialPageRoute(builder: (_) => const DetailPage()),
  ),
  child: Hero(
    tag: 'hero-image',
    child: Image.asset('assets/card.jpg', width: 100),
  ),
)

// Detail page
Hero(
  tag: 'hero-image',  // same tag connects them
  child: Image.asset('assets/card.jpg'),
)
Enter fullscreen mode Exit fullscreen mode

Flutter generates the transition automatically.

AnimatedContainer: Declarative Animations

No AnimationController needed. State changes animate automatically:

bool _isExpanded = false;

GestureDetector(
  onTap: () => setState(() => _isExpanded = !_isExpanded),
  child: AnimatedContainer(
    duration: const Duration(milliseconds: 300),
    curve: Curves.easeInOut,
    width: _isExpanded ? 200 : 100,
    height: _isExpanded ? 200 : 100,
    color: _isExpanded ? Colors.blue : Colors.grey,
    child: const Center(child: Text('Tap me')),
  ),
)
Enter fullscreen mode Exit fullscreen mode

TweenAnimationBuilder: Custom Value Animations

TweenAnimationBuilder<double>(
  tween: Tween(begin: 0, end: _progress),
  duration: const Duration(milliseconds: 500),
  builder: (context, value, child) {
    return LinearProgressIndicator(value: value);
  },
)
Enter fullscreen mode Exit fullscreen mode

When _progress changes, the widget re-animates automatically.

Choosing the Right Tool

Simple state change → AnimatedContainer (declarative, fastest)
Custom value animation → TweenAnimationBuilder
Fine-grained control → AnimationController + Tween
Screen transition → Hero
Enter fullscreen mode Exit fullscreen mode

Start with AnimatedContainer. Graduate to AnimationController only when you need control that declarative APIs can't provide. Keep it simple — smooth animations improve UX without costing development time.

Top comments (0)