Flutter Tutorial: Animated Icons | Creating an Animated Icon in Flutter

Learn how to use the AnimatedIcon widget in Flutter to create engaging animations for your app's icons. This tutorial covers how to set up the widget, define your animation controller, and configure your icon's animation parameters. 

With these steps, you'll be able to create custom animated icons that respond to user interactions and add dynamic flair to your Flutter app.


  • In this example we have stateful widget called ButtonAnimation and a stateful widget called SingleAnimatedIcon.
  • The ButtonAnimation widget is a screen containing several SingleAnimatedIcon widgets. When a user taps on a SingleAnimatedIcon, the icon will animate in a different way.
  • SingleAnimatedIcon is a widget that displays a single AnimatedIcon and has two boolean variables to determine the appearance of the icon. The variables are isBackground and isSlowMotion.
  • The SingleAnimatedIcon widget is used several times in the ButtonAnimation widget, with different AnimatedIconData passed in as the animatedIconData variable. The animatedIconData variable is used to determine which icon to display in the AnimatedIcon.

Demo 1: Collection of animation icon

import 'package:flutter/material.dart';
import '../../data/my_colors.dart';

// This code defines a stateful widget that displays several variations of
// Flutter's AnimatedIcon class. The widget uses several helper classes and
// arrays to customize the appearance of each icon and animate them in slow
// motion if desired. The code also defines a reusable widget called
// SingleAnimatedIcon that simplifies the process of displaying a single
// AnimatedIcon.
class ButtonAnimation extends StatefulWidget {
  const ButtonAnimation({super.key});
  @override
  ButtonAnimationState createState() => ButtonAnimationState();
}
class ButtonAnimationState extends State<ButtonAnimation> {
  List<bool> isSelectedTab = List.generate(3, (index) => false);
  List<bool> isSelectedIcon = List.generate(3, (index) => false);
  List<bool> isSelectedText = List.generate(4, (index) => false);
  List<bool> isSelectedAlign = List.generate(3, (index) => false);
  List<bool> isSelectedTabIcon = List.generate(2, (index) => false);

  @override
  void initState() {
    isSelectedTab[1] = true;
    isSelectedIcon[1] = true;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
     // appBar: CommonAppBar.getPrimaryBackAppbarNew(context, "Animating Icons") as PreferredSizeWidget?,
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(15),
        scrollDirection: Axis.vertical,
        child: Column(
          children: <Widget>[

            //.................................................................
            Container(
              margin: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[

                  const Text("Animated Icon"),
                  Container(
                    margin: const EdgeInsets.only(top: 8),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: const <Widget>[

                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.play_pause,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.add_event,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.arrow_menu,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.ellipsis_search,
                        ),
                      ],
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 16),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: const <Widget>[
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.home_menu,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.view_list,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.menu_close,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.menu_arrow,
                        ),
                      ],
                    ),
                  ),

                  Container(
                    margin: const EdgeInsets.only(top: 24),
                    child:
                    const Text("Animated Icon with background"),
                  ),

                  Container(
                    margin: const EdgeInsets.only(top: 24),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: const <Widget>[
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.play_pause,
                          isBackground: true,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.add_event,
                          isBackground: true,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.arrow_menu,
                          isBackground: true,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.ellipsis_search,
                          isBackground: true,
                        ),
                      ],
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 16),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: const <Widget>[
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.home_menu,
                          isBackground: true,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.view_list,
                          isBackground: true,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.menu_close,
                          isBackground: true,
                        ),
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.menu_arrow,
                          isBackground: true,
                        ),
                      ],
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 24),
                    child:
                    const Text("Slow Motion"),
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 16),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: const <Widget>[
                        SingleAnimatedIcon(
                          animatedIconData: AnimatedIcons.play_pause,
                          isBackground: true,
                          isSlowMotion: true,
                        ),
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.add_event,
                            isBackground: true,
                            isSlowMotion: true),
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.arrow_menu,
                            isBackground: true,
                            isSlowMotion: true),
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.ellipsis_search,
                            isBackground: true,
                            isSlowMotion: true),
                      ],
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 16),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: const <Widget>[
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.home_menu,
                            isBackground: true,
                            isSlowMotion: true),
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.view_list,
                            isBackground: true,
                            isSlowMotion: true),
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.menu_close,
                            isBackground: true,
                            isSlowMotion: true),
                        SingleAnimatedIcon(
                            animatedIconData: AnimatedIcons.menu_arrow,
                            isBackground: true,
                            isSlowMotion: true),
                      ],
                    ),
                  ),
                ],
              ),
            ),

          ],
        ),
      ),
    );
  }
}

class SingleAnimatedIcon extends StatefulWidget {
  // Widget to display a single AnimatedIcon
  final AnimatedIconData animatedIconData;
  // Data for the AnimatedIcon
  final bool isBackground, isSlowMotion;
  // Flags to determine the appearance of the icon (background and slow motion)

  // Constructor
  const SingleAnimatedIcon(
      {Key? key,
        required this.animatedIconData,
        this.isBackground = false,
        this.isSlowMotion = false})
      : super(key: key);

  @override
  SingleAnimatedIconState createState() => SingleAnimatedIconState();
}

class SingleAnimatedIconState extends State<SingleAnimatedIcon>
    with SingleTickerProviderStateMixin {
  // State of the SingleAnimatedIcon widget
  late ThemeData theme;
  // Theme data for the widget
  late AnimationController _animationController;
  // Animation controller for the AnimatedIcon
  bool isPlaying = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
        vsync: this,
        // Setting the vsync to this makes the animation controller
        // depend on this state
        duration: Duration(milliseconds: widget.isSlowMotion ? 1500 : 400));
    // The duration of the animation is set based on the isSlowMotion flag
  }

  @override
  Widget build(BuildContext context) {
    Color primaryColor = Theme.of(context).colorScheme.primary;
    theme = Theme.of(context);
    return ClipOval(
      // Clipping the widget to an oval shape
      child: Material(
        color: widget.isBackground
            ? MyColors.primary.withAlpha(20)
            : Colors.transparent, // button color
        child: InkWell(
          splashColor: primaryColor,
          // inkwell color
          child: SizedBox(
              width: 44,
              height: 44,
              child: IconButton(
                iconSize: 24,
                icon: AnimatedIcon(
                  icon: widget.animatedIconData,
                  progress: _animationController,
                  color: primaryColor,
                ),
                // Animated icon with the animation controller's progress
                onPressed: () {
                  if (isPlaying) {
                    _animationController.reverse();
                    setState(() {
                      isPlaying = false;
                    });
                  } else {
                    _animationController.forward();
                    setState(() {
                      isPlaying = true;
                    });
                  }
                },
                // Toggling the animation playback on button press
              )),
          onTap: () {},
        ),
      ),
    );
  }
}

..

class MyColors {

  static const Color primary = Color(0xFF12376F);
}

..

Demo 2: Flutter Animated Icon Example

This Flutter example demonstrates how to create an animated icon using the AnimatedIcon widget. The icon used in this example is the AnimatedIcons.menu_arrow, which animates between the menu and arrow icons. The animation is looped back and forth continuously using an AnimationController and the repeat method. The example also shows how to specify the size and semantic label for the animated icon.

import 'package:flutter/material.dart';

void main() {
  runApp(const AnimatedIconApp());
}

class AnimatedIconApp extends StatelessWidget {
  const AnimatedIconApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // set theme data for the app
      theme: ThemeData(
        colorSchemeSeed: const Color(0xff6750a4),
        useMaterial3: true,
      ),
      home: const Scaffold(
        // set the body of the scaffold to the AnimatedIconExample widget
        body: AnimatedIconExample(),
      ),
    );
  }
}

class AnimatedIconExample extends StatefulWidget {
  const AnimatedIconExample({super.key});

  @override
  State<AnimatedIconExample> createState() => _AnimatedIconExampleState();
}

class _AnimatedIconExampleState extends State<AnimatedIconExample>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    // create an animation controller with a duration of 2 seconds
    controller = AnimationController(
      vsync: this, // the ticker provider
      duration: const Duration(seconds: 2),
    )
      ..forward() // start the animation by moving it forward
      ..repeat(reverse: true); // loop the animation forward and backward
    // create a tween to interpolate between 0.0 and 1.0 over the animation duration
    animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
  }

  @override
  void dispose() {
    // dispose of the animation controller when the widget is removed from the tree
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedIcon(
          // set the icon to be an animated menu arrow
          icon: AnimatedIcons.menu_arrow,
          // set the animation progress to be the animation created in initState
          progress: animation,
          // set the size of the icon
          size: 72.0,
          // set the semantic label of the icon
          semanticLabel: 'Show menu',
        ),
      ),
    );
  }
}

..

Ref code: https://api.flutter.dev/flutter/material/AnimatedIcon-class.html

Comments