Explicit Animations

Full control over Flutter animations

🎯 What are Explicit Animations?

Explicit animations give you complete control over animation timing and behavior using AnimationController. They're perfect for complex animations, repeating effects, and custom motion patterns requiring precise control over start, stop, reverse, and repeat actions.


// Full control with AnimationController
AnimationController controller = AnimationController(
  duration: Duration(seconds: 2),
  vsync: this,
);
controller.forward();
                                    

Key Components

🎮

AnimationController

Controls animation playback

controller.forward()
controller.reverse()
controller.repeat()
📊

Tween

Defines animation range

Tween(
  begin: 0.0,
  end: 300.0,
)
🔄

AnimatedBuilder

Rebuilds on animation changes

AnimatedBuilder(
  animation: controller,
  builder: (context, child) {},
)
⏱️

TickerProvider

Provides animation timing

with SingleTickerProviderStateMixin

🔹 Basic AnimationController Setup

Setting up an explicit animation:

class ExplicitAnimationDemo extends StatefulWidget {
  @override
  _ExplicitAnimationDemoState createState() => _ExplicitAnimationDemoState();
}

class _ExplicitAnimationDemoState extends State
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  late Animation _animation;

  @override
  void initState() {
    super.initState();
    
    // Create controller
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    // Create animation with Tween
    _animation = Tween(
      begin: 0.0,
      end: 300.0,
    ).animate(_controller);
  }

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

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

🔹 Controlling Animations

Different ways to control animation playback:

class AnimationControls extends StatefulWidget {
  @override
  _AnimationControlsState createState() => _AnimationControlsState();
}

class _AnimationControlsState extends State
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () => _controller.forward(),
          child: Text('Play Forward'),
        ),
        ElevatedButton(
          onPressed: () => _controller.reverse(),
          child: Text('Play Reverse'),
        ),
        ElevatedButton(
          onPressed: () => _controller.repeat(),
          child: Text('Repeat'),
        ),
        ElevatedButton(
          onPressed: () => _controller.stop(),
          child: Text('Stop'),
        ),
        ElevatedButton(
          onPressed: () => _controller.reset(),
          child: Text('Reset'),
        ),
      ],
    );
  }

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

🔹 Rotating Animation Example

Create a continuously rotating widget:

class RotatingSquare extends StatefulWidget {
  @override
  _RotatingSquareState createState() => _RotatingSquareState();
}

class _RotatingSquareState extends State
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 3),
      vsync: this,
    )..repeat(); // Automatically repeat
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.rotate(
          angle: _controller.value * 2 * 3.14159, // Full rotation
          child: Container(
            width: 100,
            height: 100,
            color: Colors.red,
            child: Center(
              child: Text(
                'Spin',
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
        );
      },
    );
  }

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

🔹 Curved Animation Example

Add curves to explicit animations:

class CurvedAnimationDemo extends StatefulWidget {
  @override
  _CurvedAnimationDemoState createState() => _CurvedAnimationDemoState();
}

class _CurvedAnimationDemoState extends State
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  late Animation _animation;

  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    // Add curve to animation
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.bounceOut,
    );
    
    // Apply Tween
    _animation = Tween(
      begin: 50.0,
      end: 300.0,
    ).animate(_animation);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value,
              height: 100,
              color: Colors.green,
            );
          },
        ),
        ElevatedButton(
          onPressed: () {
            _controller.reset();
            _controller.forward();
          },
          child: Text('Animate'),
        ),
      ],
    );
  }

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

🔹 Multiple Tweens Example

Animate multiple properties simultaneously:

class MultiTweenAnimation extends StatefulWidget {
  @override
  _MultiTweenAnimationState createState() => _MultiTweenAnimationState();
}

class _MultiTweenAnimationState extends State
    with SingleTickerProviderStateMixin {
  
  late AnimationController _controller;
  late Animation _sizeAnimation;
  late Animation _colorAnimation;

  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    // Size animation
    _sizeAnimation = Tween(
      begin: 50.0,
      end: 200.0,
    ).animate(_controller);
    
    // Color animation
    _colorAnimation = ColorTween(
      begin: Colors.blue,
      end: Colors.purple,
    ).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Container(
              width: _sizeAnimation.value,
              height: _sizeAnimation.value,
              decoration: BoxDecoration(
                color: _colorAnimation.value,
                borderRadius: BorderRadius.circular(20),
              ),
            );
          },
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () {
            if (_controller.status == AnimationStatus.completed) {
              _controller.reverse();
            } else {
              _controller.forward();
            }
          },
          child: Text('Toggle Animation'),
        ),
      ],
    );
  }

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

🔹 Animation Status Listeners

React to animation state changes:

@override
void initState() {
  super.initState();
  
  _controller = AnimationController(
    duration: Duration(seconds: 2),
    vsync: this,
  );
  
  // Listen to status changes
  _controller.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      print('Animation completed!');
      _controller.reverse();
    } else if (status == AnimationStatus.dismissed) {
      print('Animation dismissed!');
      _controller.forward();
    }
  });
  
  // Listen to value changes
  _controller.addListener(() {
    print('Current value: ${_controller.value}');
  });
  
  _controller.forward();
}

💡 Explicit Animation Best Practices:

  • Always dispose: Call controller.dispose() to prevent memory leaks
  • Use vsync: Required for smooth animations and battery efficiency
  • SingleTickerProvider: Use for one controller, MultiTickerProvider for multiple
  • AnimatedBuilder: Optimizes rebuilds to only animated widgets
  • Status listeners: Use to chain animations or trigger actions

🧠 Test Your Knowledge

What mixin is required for AnimationController?