Skip to main content

Implementing Advanced Animations

1. Using AnimatedBuilder Widget

Property Description Values
child Child widget to be built Widget?
animation Animation controller driving the animation Animation<double>
builder Function that builds the widget Widget Function(BuildContext, Widget?)

Example: Basic AnimatedBuilder Usage

class AnimatedBuilderExample extends StatefulWidget {
  @override
  _AnimatedBuilderExampleState createState() => _AnimatedBuilderExampleState();
}

class _AnimatedBuilderExampleState extends State<AnimatedBuilderExample> with TickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    animation = Tween<double>(begin: 0, end: 1).animate(controller);
    controller.repeat(reverse: true);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      child: Icon(Icons.favorite, size: 50),
      builder: (context, child) {
        return Transform.rotate(
          angle: animation.value * 2 * pi,
          child: child,
        );
      },
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
Note: AnimatedBuilder is more efficient than rebuilding the entire widget tree when only a part of it needs to change.

2. Creating Staggered Animations

Staggered animations apply delays to sequential animation effects, creating a cascading or ripple-like visual effect. This is commonly used in list item animations or UI component reveal sequences.

Property Description Values
delay Time in milliseconds before the animation starts int
duration Total duration of the animation in milliseconds int

Example: Staggered Fade Animation

class StaggeredFadeAnimation extends StatefulWidget {
  @override
  _StaggeredFadeAnimationState createState() => _StaggeredFadeAnimationState();
}

class _StaggeredFadeAnimationState extends State<StaggeredFadeAnimation> with TickerProviderStateMixin {
  late AnimationController controller;
  late List<AnimationController> controllers;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: Duration(milliseconds: 1000),
      vsync: this,
    );
    controllers = List.generate(5, (index) => AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    ));
  }

  @override
  void dispose() {
    controller.dispose();
    for (var c in controllers) c.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: List.generate(5, (index) {
        final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
          CurvedAnimation(parent: controllers[index], curve: Curves.easeIn),
        );
        return FadeTransition(
          opacity: animation,
          child: Container(
            height: 50,
            margin: EdgeInsets.symmetric(vertical: 5),
            color: Colors.blue,
          ),
        );
      }),
    );
  }
}
Note: Use CurvedAnimation to apply easing curves to staggered animations for smoother transitions.

Staggered Animation Best Practices

  • Use AnimationController with individual delays for each widget.
  • Apply CurvedAnimation to enhance visual flow.
  • Limit the number of staggered elements to prevent performance issues.

3. Using AnimatedWidget Class

The AnimatedWidget is a powerful base class in Flutter that simplifies the creation of animations by automatically handling the animation loop and providing a clean way to rebuild widgets when animation values change.

Property Description Usage
child The child widget to be animated. Optional, but commonly used for passing in a widget that needs animation.
animation The Animation<double> object that controls the animation. Mandatory. Must be provided to drive the animation state.

Example: Basic AnimatedWidget Usage

import 'package:flutter/material.dart';

class AnimatedCircle extends AnimatedWidget {
  AnimatedCircle({Key? key, required Animation<double> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable as Animation<double>;
    return Container(
      width: 100 * animation.value,
      height: 100 * animation.value,
      decoration: BoxDecoration(
        color: Colors.blue,
        shape: BoxShape.circle,
      ),
    );
  }
}

class AnimationExample extends StatefulWidget {
  @override
  _AnimationExampleState createState() => _AnimationExampleState();
}

class _AnimationExampleState extends State<AnimationExample> with TickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
    controller.repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("AnimatedWidget Example")),
      body: Center(
        child: AnimatedCircle(animation: animation),
      ),
    );
  }
}
Note: AnimatedWidget is ideal for animating a single widget that depends on one or more animations. It's particularly useful when you want to encapsulate animation logic into a reusable component.

4. Creating Custom Animated Widgets

Property Description Example
child The widget to be animated Text('Animated Text')
duration Duration of the animation in milliseconds Duration(milliseconds: 500)
curve The animation curve to use (e.g., Curves.easeInOut) Curves.easeInOut

Example: Custom Animated Widget

class CustomAnimatedWidget extends AnimatedWidget {
  final Animation<double> animation;

  CustomAnimatedWidget({required this.animation}) : super(listenable: animation);

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

Example: Using Custom Animated Widget

class AnimationExample extends StatefulWidget {
  @override
  _AnimationExampleState createState() => _AnimationExampleState();
}

class _AnimationExampleState extends State<AnimationExample>
    with TickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    animation = Tween<double>(begin: 1.0, end: 2.0).animate(
      CurvedAnimation(parent: controller, curve: Curves.easeInOut),
    );
    controller.repeat(reverse: true);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Custom Animated Widget')),
      body: Center(
        child: CustomAnimatedWidget(animation: animation),
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
Note: The AnimatedWidget is a powerful way to create custom animated widgets that can be reused across your app.

5. Implementing Physics-Based Animations

Physics-based animations in Flutter simulate real-world physics to create natural, dynamic motion. These animations use the physics package or custom physics engines to model forces like gravity, friction, and elasticity.

Property Description Values
spring Defines spring physics properties for animation SpringDescription
gravity Gravity force applied to animated objects double
friction Resistance to motion over time double
elasticity How much bounce occurs on impact double

Example: Spring Animation

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: AnimatedBuilder(
            animation: springAnimation,
            builder: (context, child) {
              return Transform.translate(
                offset: Offset(springAnimation.value, 0),
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

final springAnimation = Tween<double>(begin: 0, end: 200).animate(
  CurvedAnimation(parent: AnimationController(vsync: MyHomePage(), duration: Duration(seconds: 2)), curve: Curves.elasticOut),
);

Example: Gravity Simulation

import 'package:flutter/material.dart';

class GravityAnimation extends StatefulWidget {
  @override
  _GravityAnimationState createState() => _GravityAnimationState();
}

class _GravityAnimationState extends State<GravityAnimation> with TickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 5),
    );
    animation = Tween<double>(begin: 0, end: 300).animate(
      CurvedAnimation(parent: controller, curve: Curves.easeOut),
    );
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        return Transform.translate(
          offset: Offset(0, animation.value),
          child: Container(
            width: 50,
            height: 50,
            color: Colors.red,
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
Note: Physics-based animations require careful tuning of parameters to achieve desired realism. Use AnimationController and CurvedAnimation to control timing and easing functions.

Key Concepts

  • Spring Physics: Simulates spring-like behavior with customizable stiffness and damping.
  • Gravity: Applies downward force to simulate real-world gravitational pull.
  • Friction: Reduces velocity over time to simulate resistance in motion.
  • Elasticity: Controls how much bounce occurs when objects collide.

6. Using AnimatedCrossFade Widget

The AnimatedCrossFade widget is a powerful tool for animating between two child widgets, smoothly transitioning from one to the other. It's particularly useful when you want to show different content based on a condition, such as switching between a loading indicator and the actual content.

Property Description Values
firstChild The widget to show when firstCurve is active. Widget
secondChild The widget to show when secondCurve is active. Widget
firstCurve The curve to use for animating from the first child to the second. AnimatedSwitcherTransitionDelegate
secondCurve The curve to use for animating from the second child to the first. AnimatedSwitcherTransitionDelegate
duration The duration of the animation. Duration
layoutBuilder Customizes how the widgets are laid out during the animation. AnimatedCrossFadeLayoutBuilder

Example: Basic usage of AnimatedCrossFade

class AnimatedCrossFadeExample extends StatefulWidget {
  @override
  _AnimatedCrossFadeExampleState createState() => _AnimatedCrossFadeExampleState();
}

class _AnimatedCrossFadeExampleState extends State<AnimatedCrossFadeExample> {
  bool showFirst = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedCrossFade Example')),
      body: Center(
        child: AnimatedCrossFade(
          firstChild: Container(
            height: 200,
            width: 200,
            color: Colors.red,
            child: Center(child: Text('First Widget')),
          ),
          secondChild: Container(
            height: 200,
            width: 200,
            color: Colors.blue,
            child: Center(child: Text('Second Widget')),
          ),
          crossFadeState: showFirst
              ? CrossFadeState.showFirst
              : CrossFadeState.showSecond,
          duration: Duration(seconds: 1),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            showFirst = !showFirst;
          });
        },
        child: Icon(Icons.refresh),
      ),
    );
  }
}
Note: The AnimatedCrossFade widget is ideal for scenarios where you want to animate between two different widgets, such as switching between a loading indicator and the actual content.

7. Using AnimatedSwitcher Widget

Property Values Description
child Widget The widget to be animated when switched.
transitionBuilder (Widget child, Animation<double> animation) Builder function to define how the child widget transitions.
duration Duration Duration of the animation (default is 300ms).
reverseDuration Duration Duration of the reverse animation (default is 300ms).

Example: Basic AnimatedSwitcher

AnimatedSwitcher(
  duration: Duration(milliseconds: 300),
  transitionBuilder: (Widget child, Animation<double> animation) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  },
  child: Text(
    'Hello World',
    key: ValueKey('hello'),
  ),
)

Example: Switching Between Widgets

AnimatedSwitcher(
  duration: Duration(milliseconds: 500),
  transitionBuilder: (Widget child, Animation<double> animation) {
    return SizeTransition(
      sizeFactor: animation,
      child: child,
    );
  },
  child: isShowingText
      ? Text('Flutter is awesome!', key: ValueKey('text'))
      : Text('Hello Flutter!', key: ValueKey('hello')),
)
Note: The AnimatedSwitcher widget requires a unique key for each child to properly detect changes and animate transitions.

8. Using Hero Animations

Hero animations in Flutter provide a seamless transition between screens, making the user experience more fluid and engaging. They are particularly useful when navigating from a list screen to a detail screen, where an element appears to "fly" between the two screens.

Property Description Values
tag Unique identifier for the Hero animation. Object
child The widget to animate between screens. Widget
transitionDuration Duration of the animation transition. Duration

Example: Basic Hero Animation

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HeroListScreen(),
    );
  }
}

class HeroListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Hero List")),
      body: ListView.builder(
        itemCount: 20,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text("Item $index"),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => HeroDetailScreen(item: index),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class HeroDetailScreen extends StatelessWidget {
  final int item;

  HeroDetailScreen({required this.item});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Item Detail")),
      body: Center(
        child: Hero(
          tag: item,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: Center(
              child: Text(
                "Item $item",
                style: TextStyle(fontSize: 24),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Note: The tag must be unique across the app to ensure proper animation matching.
Warning: Hero animations only work between two screens if the widgets with matching tags are of the same type.

9. Using AnimatedList Widget

The AnimatedList widget in Flutter is a powerful tool for animating lists with dynamic content. It provides built-in animations for insertions, removals, and reordering of list items.

Property Description Values
key Unique identifier for the widget Key?
itemBuilder Builder function to create list items IndexedWidgetBuilder
initialItemCount Initial number of items in the list int
scrollDirection Direction of scrolling Axis.horizontal or Axis.vertical
reverse Reverse the scroll direction bool
controller Scroll controller to manage scrolling ScrollController?
primary Whether the list is the primary scrollable bool?
physics Scroll physics to control scrolling behavior ScrollPhysics?

Example: Adding Items to AnimatedList

class AnimatedListExample extends StatefulWidget {
  @override
  _AnimatedListExampleState createState() => _AnimatedListExampleState();
}

class _AnimatedListExampleState extends State<AnimatedListExample> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  List<String> _items = [];

  void _addItem() {
    final newItem = 'Item ${_items.length + 1}';
    _items.insert(0, newItem);
    _listKey.currentState!.insertItem(0);
  }

  Widget _buildItem(BuildContext context, int index) {
    return ListTile(
      title: Text(_items[index]),
      trailing: IconButton(
        icon: Icon(Icons.delete),
        onPressed: () {
          final removedItem = _items.removeAt(index);
          _listKey.currentState!.removeItem(
            index,
            (context, animation) => _buildRemovedItem(removedItem, animation),
          );
        },
      ),
    );
  }

  Widget _buildRemovedItem(String item, Animation<double> animation) {
    return SlideTransition(
      position: Tween<Offset>(begin: Offset.zero, end: Offset(0, -1))
          .animate(animation),
      child: ListTile(
        title: Text(item),
        subtitle: Text('Removed'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedList Example')),
      body: AnimatedList(
        key: _listKey,
        initialItemCount: _items.length,
        itemBuilder: _buildItem,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: Icon(Icons.add),
      ),
    );
  }
}
Note: The AnimatedList widget requires an AnimatedListState to manage item insertions and removals. The itemBuilder function is responsible for creating each list item, and the insertItem and removeItem methods provide built-in animations for these operations.

Key Features of AnimatedList

  • Dynamic Updates: Efficiently animates list items when added or removed
  • Custom Animations: Allows custom animations for item removals using removeItem
  • Scrolling: Supports horizontal and vertical scrolling with custom physics

10. Using Rive Animations

Rive is a powerful animation platform that allows developers to integrate high-quality animations into Flutter apps with ease.

Feature Description Notes
rive_flutter rive_flutter: ^0.5.0 Official Flutter package for Rive animations
RiveAnimation RiveAnimation.asset('assets/animation.riv') Load Rive animation from asset
RiveAnimation.play RiveAnimation.play('animationName') Play specific animation by name

Example: Loading a Rive Animation

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class RiveExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Rive Animation')),
      body: Center(
        child: RiveAnimation.asset('assets/animation.riv'),
      ),
    );
  }
}

Example: Controlling Animation Playback

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class PlayableRiveExample extends StatefulWidget {
  @override
  _PlayableRiveExampleState createState() => _PlayableRiveExampleState();
}

class _PlayableRiveExampleState extends State<PlayableRiveExample> {
  bool isPlaying = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Controlled Rive')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            RiveAnimation.asset('assets/animation.riv'),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  isPlaying = !isPlaying;
                });
              },
              child: Text(isPlaying ? 'Pause' : 'Play'),
            ),
          ],
        ),
      ),
    );
  }
}
Note: Ensure that the Rive file is added to your pubspec.yaml under flutter: and assets:.
Warning: Rive animations require the rive_flutter package and must be properly configured in your project.

Key Features of Rive in Flutter

  • NEW Seamless integration with Flutter UI components
  • Supports both vector and bitmap animations
  • Animation control via state machines or direct playback
  • High performance rendering engine optimized for mobile

Was this article helpful?