Understanding Different Types of Animations Available in Flutter

Animations are a vital component of modern mobile applications, enhancing user experience by providing visual feedback and making apps feel more fluid and engaging. Flutter, Google’s UI toolkit, offers a rich set of animation capabilities, making it easy for developers to create beautiful and performant animations. This blog post explores the different types of animations available in Flutter, providing code examples and use cases.

Why Animations Matter in Flutter

Animations can significantly improve the user experience of a Flutter app by:

  • Providing Visual Feedback: Confirming user actions or indicating progress.
  • Enhancing Engagement: Making the UI more interesting and enjoyable.
  • Improving Navigation: Guiding users through the app and reducing cognitive load.

Types of Animations in Flutter

Flutter provides several types of animations, each suited for different purposes:

  • Implicit Animations
  • Explicit Animations
  • Hero Animations
  • Animated List

1. Implicit Animations

Implicit animations are the simplest type to implement in Flutter. They automatically animate changes to a widget’s properties using the Animated family of widgets.

Key Concepts
  • Animated Widgets: Widgets like AnimatedContainer, AnimatedOpacity, AnimatedPadding, etc., automatically animate property changes.
  • Automatic Transition: When a property of an Animated widget changes, Flutter smoothly transitions from the old value to the new value.
Example: AnimatedContainer

The AnimatedContainer widget is used to animate changes to its size, color, padding, and other properties.


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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Implicit Animation Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  double _width = 50;
  double _height = 50;
  Color _color = Colors.green;
  BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);

  void _animateContainer() {
    setState(() {
      final random = Random();
      _width = random.nextInt(300).toDouble();
      _height = random.nextInt(300).toDouble();
      _color = Color.fromRGBO(
        random.nextInt(256),
        random.nextInt(256),
        random.nextInt(256),
        1,
      );
      _borderRadius = BorderRadius.circular(random.nextInt(100).toDouble());
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedContainer Demo')),
      body: Center(
        child: AnimatedContainer(
          width: _width,
          height: _height,
          decoration: BoxDecoration(
            color: _color,
            borderRadius: _borderRadius,
          ),
          duration: Duration(milliseconds: 500),
          curve: Curves.fastOutSlowIn,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _animateContainer,
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}

In this example, tapping the FloatingActionButton triggers the _animateContainer function, which updates the width, height, color, and borderRadius properties. The AnimatedContainer then animates these changes over a duration of 500 milliseconds, using the fastOutSlowIn curve for a smooth transition.

2. Explicit Animations

Explicit animations involve more manual control over the animation process. They require using AnimationController, Animation objects, and Tween to define the animation behavior.

Key Concepts
  • AnimationController: Manages the animation’s timeline and state (playing, stopped, reversed).
  • Animation: Represents the animation’s current value as a function of time.
  • Tween: Defines the range of values to animate between.
  • AnimatedBuilder: Rebuilds only the part of the UI that depends on the animation, improving performance.
Example: Fade Transition

This example demonstrates a fade transition using explicit animations:


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Explicit Animation Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

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

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);

    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fade Transition Demo')),
      body: Center(
        child: FadeTransition(
          opacity: _animation,
          child: Padding(
            padding: EdgeInsets.all(8),
            child: FlutterLogo(size: 150),
          ),
        ),
      ),
    );
  }
}

In this example:

  • An AnimationController named _controller manages the animation’s timeline.
  • A Tween specifies the range from 0.0 to 1.0 for the opacity.
  • The FadeTransition widget applies the animation to a FlutterLogo.

3. Hero Animations

Hero animations, also known as shared element transitions, are used to create a seamless visual connection between two screens when navigating from one to another. They are often used when an element (e.g., an image or a text label) on one screen transforms into a similar element on another screen.

Key Concepts
  • Hero Widget: Wraps the widget that will transition between screens.
  • Tag: Each Hero widget must have a unique tag that identifies the element being animated.
Example: Hero Animation

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hero Animation Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MainScreen(),
    );
  }
}

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Main Screen')),
      body: Center(
        child: InkWell(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => DetailScreen()),
            );
          },
          child: Hero(
            tag: 'flutterLogo',
            child: FlutterLogo(size: 150),
          ),
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail Screen')),
      body: Center(
        child: Hero(
          tag: 'flutterLogo',
          child: FlutterLogo(size: 300),
        ),
      ),
    );
  }
}

In this example:

  • The FlutterLogo widget is wrapped in a Hero widget in both the MainScreen and DetailScreen.
  • Both Hero widgets have the same tag (‘flutterLogo’), which tells Flutter that these two elements should be animated together during the screen transition.

4. AnimatedList

AnimatedList is a widget designed to animate the insertion and removal of items in a list. It is especially useful when you want to provide visual feedback for changes in a list’s content.

Key Concepts
  • AnimatedListState: Manages the state of the AnimatedList.
  • Insert and Remove Animations: Animations that trigger when items are added to or removed from the list.
Example: AnimatedList

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnimatedList Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  final GlobalKey _listKey = GlobalKey();
  List _items = [];

  void _addItem() {
    final String newItem = 'Item ${_items.length + 1}';
    _items.insert(0, newItem);
    _listKey.currentState?.insertItem(
      0,
      duration: const Duration(milliseconds: 500),
    );
  }

  void _removeItem(int index) {
    final String removedItem = _items[index];
    _listKey.currentState?.removeItem(
      index,
      (context, animation) => ListItem(
        animation: animation,
        item: removedItem,
        onClicked: () {}, // Dummy callback
      ),
      duration: const Duration(milliseconds: 500),
    );
    _items.removeAt(index);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimatedList Demo')),
      body: AnimatedList(
        key: _listKey,
        initialItemCount: _items.length,
        itemBuilder: (context, index, animation) {
          return ListItem(
            animation: animation,
            item: _items[index],
            onClicked: () => _removeItem(index),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: Icon(Icons.add),
      ),
    );
  }
}

class ListItem extends StatelessWidget {
  const ListItem({
    Key? key,
    required this.animation,
    required this.item,
    required this.onClicked,
  }) : super(key: key);

  final Animation animation;
  final String item;
  final VoidCallback onClicked;

  @override
  Widget build(BuildContext context) {
    return SizeTransition(
      sizeFactor: animation,
      child: Card(
        margin: EdgeInsets.all(10),
        color: Colors.blue[100],
        child: ListTile(
          title: Text(item),
          trailing: IconButton(
            icon: Icon(Icons.delete),
            onPressed: onClicked,
          ),
        ),
      ),
    );
  }
}

In this example:

  • _addItem inserts a new item into the list and triggers an insertion animation using _listKey.currentState?.insertItem.
  • _removeItem removes an item from the list and triggers a removal animation using _listKey.currentState?.removeItem.
  • The ListItem widget animates the appearance and disappearance of list items using the SizeTransition widget.

Best Practices for Flutter Animations

  • Optimize Performance: Use AnimatedBuilder and RepaintBoundary to minimize unnecessary rebuilds.
  • Use Curves: Experiment with different curve types to achieve the desired animation effect.
  • Keep Animations Short and Sweet: Avoid animations that are too long or complex, as they can be distracting and impact performance.
  • Provide Meaningful Transitions: Ensure animations enhance the user experience and provide clear visual feedback.

Conclusion

Flutter offers a diverse range of animation capabilities, from simple implicit animations to more complex explicit animations, hero transitions, and animated lists. Understanding these different types of animations and how to implement them effectively can greatly enhance the user experience of your Flutter applications, making them more engaging and visually appealing. By following best practices and optimizing for performance, you can create beautiful and performant animations that delight your users.