Back to Blog
Flutter Development

Creating Smooth Animations with Flutter

Comprehensive guide to implementing complex animations in Flutter using AnimationController, Tween, and custom animation widgets.

D
Don Wilson
Mobile Developer
May 28, 2025
10 min read
Creating Smooth Animations with Flutter

Animations bring life to your Flutter applications, creating engaging and delightful user experiences. This guide covers everything from basic animations to complex, coordinated motion.

Animation Fundamentals

Flutter's animation system is built on several key concepts that work together to create smooth, performant animations.

Core Animation Classes

  • AnimationController: Controls the animation's duration and playback
  • Tween: Defines the range of values for the animation
  • Curve: Determines the animation's easing function
  • Animation: The animated value that changes over time

Basic Animation Example

Here's a simple fade-in animation:

class FadeInWidget extends StatefulWidget {
  final Widget child;
  
  FadeInWidget({required this.child});
  
  @override
  _FadeInWidgetState createState() => _FadeInWidgetState();
}

class _FadeInWidgetState extends State<FadeInWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );
    
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeIn),
    );
    
    _controller.forward();
  }
  
  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: widget.child,
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Advanced Animation Techniques

Staggered Animations

Create complex animations by coordinating multiple animated properties:

class StaggeredAnimation extends StatelessWidget {
  final Animation<double> controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  
  StaggeredAnimation({required this.controller})
      : opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
          CurvedAnimation(
            parent: controller,
            curve: Interval(0.0, 0.3, curve: Curves.ease),
          ),
        ),
        width = Tween<double>(begin: 50.0, end: 200.0).animate(
          CurvedAnimation(
            parent: controller,
            curve: Interval(0.3, 0.6, curve: Curves.ease),
          ),
        ),
        height = Tween<double>(begin: 50.0, end: 200.0).animate(
          CurvedAnimation(
            parent: controller,
            curve: Interval(0.6, 1.0, curve: Curves.ease),
          ),
        );
        
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        return Opacity(
          opacity: opacity.value,
          child: Container(
            width: width.value,
            height: height.value,
            color: Colors.blue,
          ),
        );
      },
    );
  }
}

Hero Animations

Create smooth transitions between screens with Hero widgets:

// First Screen
Hero(
  tag: 'image-hero',
  child: Image.asset('assets/image.png'),
)

// Second Screen
Hero(
  tag: 'image-hero',
  child: Image.asset('assets/image.png'),
)

Performance Best Practices

  • Use const constructors: Reduce widget rebuilds
  • Avoid expensive operations: Keep build methods lightweight
  • Use RepaintBoundary: Isolate animated widgets
  • Profile your animations: Use Flutter DevTools to identify bottlenecks

Custom Implicit Animations

Create reusable animated widgets with ImplicitlyAnimatedWidget:

class AnimatedColorBox extends ImplicitlyAnimatedWidget {
  final Color color;
  final Widget? child;
  
  AnimatedColorBox({
    required this.color,
    this.child,
    Duration duration = const Duration(milliseconds: 300),
    Curve curve = Curves.easeInOut,
  }) : super(duration: duration, curve: curve);
  
  @override
  AnimatedColorBoxState createState() => AnimatedColorBoxState();
}

class AnimatedColorBoxState 
    extends AnimatedWidgetBaseState<AnimatedColorBox> {
  ColorTween? _colorTween;
  
  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    _colorTween = visitor(
      _colorTween,
      widget.color,
      (value) => ColorTween(begin: value as Color),
    ) as ColorTween?;
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      color: _colorTween?.evaluate(animation),
      child: widget.child,
    );
  }
}

Conclusion

Mastering Flutter animations opens up endless possibilities for creating engaging user experiences. Start with simple animations and gradually build up to more complex interactions.

Remember:

  • Always dispose of animation controllers
  • Use the right animation type for your use case
  • Test animations on real devices
  • Keep performance in mind

Tags

FlutterAnimationsUI/UX

Share this article