Flutter Animations Basics

Animations make apps feel alive and polished. Flutter has a rich animation system. This topic covers implicit animations (simple, automatic) and explicit animations (full control), so you know which tool to use for each situation.

Two Types of Animations in Flutter

  Implicit Animations:
  ───────────────────────────────────────────────────────
  You change a value. Flutter animates the transition.
  Low code. Great for simple effects.
  Example: AnimatedContainer, AnimatedOpacity

  Explicit Animations:
  ───────────────────────────────────────────────────────
  You control start, stop, repeat, and direction.
  More code. Great for complex or looping effects.
  Example: AnimationController + Tween

Implicit Animations — AnimatedContainer

AnimatedContainer automatically animates any property that changes — size, color, padding, border radius.

class GrowingBox extends StatefulWidget {
  @override
  State<GrowingBox> createState() => _GrowingBoxState();
}

class _GrowingBoxState extends State<GrowingBox> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() { _expanded = !_expanded; });
      },
      child: AnimatedContainer(
        duration: Duration(milliseconds: 400),
        curve: Curves.easeInOut,
        width: _expanded ? 250 : 100,
        height: _expanded ? 250 : 100,
        color: _expanded ? Colors.indigo : Colors.blue,
        child: Center(child: Text(_expanded ? 'Big!' : 'Tap', style: TextStyle(color: Colors.white))),
      ),
    );
  }
}
  Before tap:          After tap:
  ┌────────┐           ┌──────────────────────┐
  │  Tap   │  ──►      │                      │
  └────────┘           │         Big!         │
  (100×100, blue)      │                      │
                       └──────────────────────┘
                       (250×250, indigo)

AnimatedOpacity — Fade In / Fade Out

class FadeText extends StatefulWidget {
  @override
  State<FadeText> createState() => _FadeTextState();
}

class _FadeTextState extends State<FadeText> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedOpacity(
          opacity: _visible ? 1.0 : 0.0,
          duration: Duration(milliseconds: 500),
          child: Text('Hello Flutter!', style: TextStyle(fontSize: 24)),
        ),
        ElevatedButton(
          onPressed: () => setState(() { _visible = !_visible; }),
          child: Text(_visible ? 'Hide' : 'Show'),
        ),
      ],
    );
  }
}

AnimatedSwitcher — Cross-Fade Between Widgets

AnimatedSwitcher(
  duration: Duration(milliseconds: 300),
  child: _isLoading
    ? CircularProgressIndicator(key: ValueKey('loading'))
    : Text('Done!', key: ValueKey('done')),
)

Common Implicit Animation Widgets

WidgetAnimates
AnimatedContainerSize, color, padding, border radius
AnimatedOpacityTransparency (fade in/out)
AnimatedSwitcherTransition between two widgets
AnimatedPositionedPosition inside a Stack
AnimatedAlignAlignment within a parent
TweenAnimationBuilderAny value between two endpoints

Explicit Animations — AnimationController

When you need full control — looping, reversing, pausing, or syncing multiple animations — use an AnimationController.

class PulsingDot extends StatefulWidget {
  @override
  State<PulsingDot> createState() => _PulsingDotState();
}

class _PulsingDotState extends State<PulsingDot>
    with SingleTickerProviderStateMixin {

  late AnimationController _controller;
  late Animation<double> _scaleAnim;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 800),
    )..repeat(reverse: true);  // Loops back and forth

    _scaleAnim = Tween<double>(begin: 0.8, end: 1.4).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return ScaleTransition(
      scale: _scaleAnim,
      child: Container(
        width: 60,
        height: 60,
        decoration: BoxDecoration(
          color: Colors.blue,
          shape: BoxShape.circle,
        ),
      ),
    );
  }
}
  Pulsing animation cycle:
  ─────────────────────────────────────────
  Scale: 0.8 → 1.4 → 0.8 → 1.4 → ...
         (grows and shrinks continuously)

Animation Curves

Curves control the speed profile of an animation — whether it starts fast and slows, or bounces at the end.

  Curves.linear      → constant speed ────────────►
  Curves.easeIn      → starts slow, ends fast ──────►
  Curves.easeOut     → starts fast, slows down ──────►
  Curves.easeInOut   → slow → fast → slow ──────────►
  Curves.bounceOut   → bouncy landing ──────────↓↑↓►
  Curves.elasticOut  → overshoots and snaps back ────►

Hero Animation — Shared Element Transition

A Hero animation moves a widget from one screen to another with a smooth shared transition — like a product image expanding from a list card into a detail page.

// Screen 1: Product List
Hero(
  tag: 'product_image_42',  // Unique tag
  child: Image.network('https://example.com/shoe.jpg', width: 100),
)

// Screen 2: Product Detail
Hero(
  tag: 'product_image_42',  // Same tag — Flutter animates between them
  child: Image.network('https://example.com/shoe.jpg', width: double.infinity),
)

When to Use Which Animation

NeedUse
Animate a value change on tapAnimatedContainer, AnimatedOpacity
Swap between two widgets smoothlyAnimatedSwitcher
Loop, reverse, or pause an animationAnimationController + Tween
Shared element between two screensHero animation
Complex custom paths or paintsCustomPainter + AnimationController

Leave a Comment

Your email address will not be published. Required fields are marked *