Flutter Animation Basics

This section introduces animation implementation in Flutter, including implicit animations and explicit animations.


Implicit animation

Implicit animations are animation Widgets where Flutter automatically handles the transition effects.

Example: AnimatedContainer

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

  @override
  State<AnimatedContainerExample> createState() =>
      _AnimatedContainerExampleState();
}

class _AnimatedContainerExampleState
    extends State<AnimatedContainerExample> {
  bool _isLarge = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Click to toggle size
        GestureDetector(
          onTap: () => setState(() => _isLarge = !_isLarge),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 500),  // Animation duration
            curve: Curves.easeInOut,  // Animation curve
            width: _isLarge ? 200 : 100,
            height: _isLarge ? 200 : 100,
            color: _isLarge ? Colors.blue : Colors.red,
            child: Center(
              child: Text(
                _isLarge ? 'large' : 'small',
                style: const TextStyle(color: Colors.white, fontSize: 24),
              ),
            ),
          ),
        ),
        const SizedBox(height: 20),
        ElevatedButton(
          onPressed: () => setState(() => _isLarge = !_isLarge),
          child: const Text('Switch'),
        ),
      ],
    );
  }
}

Common Implicit Animation Widgets

WidgetDescription
AnimatedContainerAnimations when properties such as container size, color, etc., change
AnimatedOpacityOpacity transition animation
AnimatedPaddingPadding transition animation
AnimatedAlignAlignment Change Animation
AnimatedSwitcherAnimation when switching child widgets
AnimatedDefaultTextStyleText Style Change Animation

Explicit animation

Explicit animations use AnimationController to fully control the animation.

Example: Rotation animation

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

  @override
  State<RotationAnimation> createState() => _RotationAnimationState();
}

class _RotationAnimationState extends State<RotationAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    // Create animation controller
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    // Create rotation animation (0 to 1 maps to 0 to 2π)
    _animation = Tween<double>(begin: 0, end: 6.28).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    );

    // Start animation
    _controller.repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return Transform.rotate(
            angle: _animation.value,  // Rotation angle
            child: const Icon(Icons.refresh, size: 100),
          );
        },
      ),
    );
  }
}

Staggered animation

Example: Staggered animation

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

  @override
  State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}

class _StaggeredAnimationState extends State<StaggeredAnimation>
    with TickerProviderStateMixin {
  late AnimationController _controller;

  // Individual animations
  late Animation<double> _fadeAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Offset> _slideAnimation;

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

    // Fade-in animation (0-0.5)
    _fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(
        parent: _controller,
        curve: const Interval(0, 0.5, curve: Curves.easeOut),
      ),
    );

    // Scale Animation (0.3-0.7)
    _scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
      CurvedAnimation(
        parent: _controller,
        curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
      ),
    );

    // Slide-in Animation (0.5-1.0)
    _slideAnimation = Tween<Offset>(
      begin: const Offset(0, 0.5),
      end: Offset.zero,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
      ),
    );

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return FadeTransition(
          opacity: _fadeAnimation,
          child: ScaleTransition(
            scale: _scaleAnimation,
            child: SlideTransition(
              position: _slideAnimation,
              child: const Card(
                child: Padding(
                  padding: EdgeInsets.all(24),
                  child: Text('Welcome'),
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

Hero animation

Hero animation is used to create shared element transition effects when switching between pages.

Example: Hero animation

// Home - image with Hero
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: GestureDetector(
          onTap: () => Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => const DetailPage()),
          ),
          child: Hero(
            tag: 'myHero',  // Same tag
            child: Image.network(
              'https://picsum.photos/200',
              width: 200,
              height: 200,
            ),
          ),
        ),
      ),
    );
  }
}

// Detail page - image with Hero
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Details')),
      body: Center(
        child: Hero(
          tag: 'myHero',  // Same tag
          child: Image.network(
            'https://picsum.photos/200',
            width: 300,
            height: 300,
          ),
        ),
      ),
    );
  }
}

Implicit animations suit simple scenarios, while explicit animations suit complex control.

Hero animation is the best choice for page transitions.

other extensions