flutter-animations

SKILL.md

Flutter Animations

Comprehensive guide to creating smooth, performant animations in Flutter using implicit animations, explicit animations, hero transitions, and custom animation patterns.

When to Use This Skill

  • Creating smooth UI transitions
  • Implementing custom animations
  • Building animated widgets
  • Performance optimization for animations
  • Hero animations between screens
  • Physics-based animations
  • Complex animation sequences
  • Gesture-driven animations

Core Animation Types

1. Implicit Animations (Recommended for Simple Cases)

// AnimatedContainer
class AnimatedContainerExample extends StatefulWidget {
  
  _AnimatedContainerExampleState createState() => _AnimatedContainerExampleState();
}

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

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _expanded = !_expanded),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        width: _expanded ? 200 : 100,
        height: _expanded ? 200 : 100,
        decoration: BoxDecoration(
          color: _expanded ? Colors.blue : Colors.red,
          borderRadius: BorderRadius.circular(_expanded ? 50 : 10),
        ),
        child: const Center(child: Text('Tap me')),
      ),
    );
  }
}

// AnimatedOpacity
AnimatedOpacity(
  opacity: _visible ? 1.0 : 0.0,
  duration: const Duration(milliseconds: 500),
  child: Container(/* ... */),
)

// AnimatedAlign
AnimatedAlign(
  alignment: _aligned ? Alignment.topLeft : Alignment.bottomRight,
  duration: const Duration(milliseconds: 300),
  child: FlutterLogo(size: 50),
)

2. Explicit Animations (Full Control)

class ExplicitAnimationExample extends StatefulWidget {
  
  _ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
}

class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

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

    _animation = Tween<double>(begin: 0, end: 300).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.easeInOut,
      ),
    );

    _controller.forward();
  }

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

  
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Container(
          width: _animation.value,
          height: _animation.value,
          color: Colors.blue,
        );
      },
    );
  }
}

3. Hero Animations

// Source screen
class SourceScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => Navigator.push(
        context,
        MaterialPageRoute(builder: (_) => DestinationScreen()),
      ),
      child: Hero(
        tag: 'hero-image',
        child: Image.network('https://example.com/image.jpg'),
      ),
    );
  }
}

// Destination screen
class DestinationScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'hero-image',
          child: Image.network('https://example.com/image.jpg'),
        ),
      ),
    );
  }
}

4. Custom Animated Widget

class FadeInWidget extends StatefulWidget {
  final Widget child;
  final Duration duration;

  const FadeInWidget({
    required this.child,
    this.duration = const Duration(milliseconds: 500),
  });

  
  _FadeInWidgetState createState() => _FadeInWidgetState();
}

class _FadeInWidgetState extends State<FadeInWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(duration: widget.duration, vsync: this);
    _animation = Tween<double>(begin: 0, end: 1).animate(_controller);
    _controller.forward();
  }

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

  
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: widget.child,
    );
  }
}

5. Staggered Animations

class StaggeredAnimation extends StatelessWidget {
  final Animation<double> controller;
  late final Animation<double> opacity;
  late final Animation<double> width;
  late final Animation<EdgeInsets> padding;

  StaggeredAnimation({required this.controller}) {
    opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.0, 0.3, curve: Curves.ease),
      ),
    );

    width = Tween<double>(begin: 50.0, end: 150.0).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.3, 0.6, curve: Curves.ease),
      ),
    );

    padding = EdgeInsetsTween(
      begin: const EdgeInsets.only(bottom: 16),
      end: const EdgeInsets.only(bottom: 75),
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.6, 1.0, curve: Curves.ease),
      ),
    );
  }

  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Container(
          padding: padding.value,
          child: Opacity(
            opacity: opacity.value,
            child: Container(
              width: width.value,
              height: width.value,
              color: Colors.blue,
            ),
          ),
        );
      },
    );
  }
}

6. Physics-Based Animations

class PhysicsAnimationExample extends StatefulWidget {
  
  _PhysicsAnimationExampleState createState() => _PhysicsAnimationExampleState();
}

class _PhysicsAnimationExampleState extends State<PhysicsAnimationExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      lowerBound: 0,
      upperBound: 500,
    );
  }

  void _runAnimation() {
    _controller.animateWith(
      SpringSimulation(
        const SpringDescription(
          mass: 1,
          stiffness: 100,
          damping: 10,
        ),
        0,
        500,
        0,
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _runAnimation,
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Transform.translate(
            offset: Offset(0, _controller.value),
            child: Container(
              width: 100,
              height: 100,
              color: Colors.blue,
            ),
          );
        },
      ),
    );
  }
}

Best Practices

  1. Use const constructors for child widgets in animations
  2. Dispose controllers properly to avoid memory leaks
  3. Use AnimatedBuilder instead of setState for better performance
  4. Prefer implicit animations for simple cases
  5. Use TweenAnimationBuilder for custom animations without controllers
  6. Test animations on real devices for performance
  7. Limit simultaneous animations to avoid jank
  8. Use RepaintBoundary to isolate expensive repaints

Resources

Weekly Installs
2
GitHub Stars
3
First Seen
Feb 21, 2026
Installed on
opencode2
gemini-cli2
claude-code2
github-copilot2
codex2
kimi-cli2