18 Jan 2024 ยท Software Engineering

    A Comprehensive Guide to Advanced Animation Techniques in Flutter

    11 min read
    Contents

    Flutter is a versatile mobile app development framework that equips developers with powerful tools to create high-quality and performant applications. One of its standout features is its robust support for animations, which can elevate your app’s user interface by making it more visually appealing and interactive.

    Introduction to Flutter Animations

    In the realm of animations, Flutter offers a range of capabilities, including physics-based animations that mimic real-world dynamics, creating more lifelike and natural movements within your app.

    This article will delve into advanced Flutter animations, exploring various types and demonstrating how to implement them in your projects.

    Prerequisites

    • Flutter
    • Dart

    Getting Started with Animations in Flutter

    Flutter’s animation system revolves around the concept of an “Animation object,” a value that evolves over time. This evolution is governed by an “AnimationController,” which defines the animation’s duration, direction, and other parameters. To set up an animation, these two elements must be connected.

    Commonly Used Animation Classes

    Below are some key classes that are frequently used in creating animations in Flutter:

    • Tween: The Tween class defines a value range for an animation to interpolate between. For instance, it can specify the animation’s start and end values.
    • AnimationController: This class controls the animation process. It allows you to initiate, halt, and restart animations, along with configuring the animation’s duration and curve.
    • AnimatedBuilder: This widget rebuilds itself whenever the animation changes. It is particularly useful for crafting complex animations involving multiple widgets.
    • Curve: Curves dictate the animation’s progression rate. Flutter offers built-in curves like LinearProgressIndicator and Curves.easeInOut, or you can design your own custom curve.

    Physics-Based Animations

    Flutter provides the Simulation class for creating physics-based simulations with initial states and evolving rules. This class empowers you to craft a wide spectrum of physics-based animations, including those based on spring dynamics, friction, and gravity.

    Let’s consider an example of a physics-based animation in Flutter: the spring animation. The SpringSimulation class can be employed to create this animation, simulating a damped harmonic oscillator. Here’s how you can use SpringSimulation to produce a spring-like animation:

    // Import required packages
    import 'package:flutter/material.dart';
    
    // Define a SpringAnimation widget
    class SpringAnimation extends StatefulWidget {
      const SpringAnimation({Key? key});
    
      @override
      _SpringAnimationState createState() => _SpringAnimationState();
    }
    
    class _SpringAnimationState extends State<SpringAnimation> with SingleTickerProviderStateMixin {
      late final AnimationController _controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 3),
      )..forward();
    
      late final Animation<double> _animation = Tween<double>(
        begin: 0,
        end: 400,
      ).animate(CurvedAnimation(
        parent: _controller,
        curve: Curves.elasticOut,
      ));
    
      void _startAnimation() {
        _controller.reset();
        _controller.forward();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: AnimatedBuilder(
                animation: _animation,
                builder: (context, child) {
                  return Stack(
                    children: [
                      Positioned(
                        left: 70,
                        top: _animation.value,
                        child: child!,
                      )
                    ],
                  );
                },
                child: GestureDetector(
                  onTap: () {
                    _startAnimation();
                  },
                  child: Container(
                    height: 100,
                    width: 250,
                    decoration: BoxDecoration(
                      color: const Color(0xFF00EF3C),
                      borderRadius: BorderRadius.circular(10),
                    ),
                    child: const Center(
                        child: Text(
                      'SEMAPHORE',
                      style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
                    )),
                  ),
                ),
              ),
            ),
          ),
        );
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    }

    In this example, the SpringAnimation widget employs an AnimationController to drive the Animation object. A Tween defines the animation’s value range, and a CurvedAnimation with Curves.elasticOut imparts an elastic-like easing curve to the animation. The AnimatedBuilder widget is utilized to animate the position of a Container widget based on the animation’s value. The Positioned widget ensures the Container widget’s position on the screen correlates with the animation’s value. As the widget is built, the AnimationController initiates the Animation object’s value animation, consequently propelling the Container widget’s position animation.

    Fig 1: Spring animation

    The outcome is a captivating spring-like animation that generates a bouncing effect on the screen. In essence, physics-based animations equip you with a potent mechanism for crafting lifelike and fluid movements in your app, and Flutter offers an array of tools and classes to facilitate their implementation.

    Hero Animation

    The Hero widget in Flutter serves as a conduit for shared element transitions between different screens. For instance, the Hero widget can animate the transition of a widget from one screen to another, encompassing diverse elements like images, text, or even containers. This widget facilitates a seamless transition by animating the shared element.

    A critical aspect of the Hero widget is the requirement for identical tags on both the origin and destination screens. This tag is pivotal in identifying the shared element undergoing the transition.

    Consider the following example illustrating the utilization of the Hero widget to attain a smooth transition between a thumbnail and a full-screen view of an image.

    Thumbnail Screen

    import 'package:flutter/material.dart';
    
    class ThumbnailScreen extends StatelessWidget {
      const ThumbnailScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Thumbnail Screen'),
          ),
          body: Column(
            children: [
              GridView.count(
                crossAxisSpacing: 15,
                crossAxisCount: 2,
                children: [
                  Hero(
                    tag: 'image1',
                    child: GestureDetector(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => const FullScreenScreen(
                               imageAsset: 'assets/white_puma.jpg',
                              heroTag: 'image1',
                            ),
                          ),
                        );
                      },
                      child: Image.asset('assets/white_puma.jpg'),
                    ),
                  ),
                  Hero(
                    tag: 'image2',
                    child: GestureDetector(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => const FullScreenScreen(
                              imageAsset: 'assets/red_nike.jpg',
                              heroTag: 'image2',
                            ),
                          ),
                        );
                      },
                      child: Image.asset('assets/red_nike.jpg',
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        );
      }
    }

    Full-Screen Screen

    class FullScreenScreen extends StatelessWidget {
      final String imageAsset;
      final String heroTag;
    
      const FullScreenScreen({super.key, required this.imageAsset, required this.heroTag});
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: GestureDetector(
            onTap: () {
              Navigator.pop(context);
            },
            child: Hero(
              tag: heroTag,
              child: Image.asset(imageAsset),
            ),
          ),
        );
      }
    }

    In the ThumbnailScreen, Hero widgets encapsulate the Image widgets, each possessing a unique tag to distinguish the shared elements. When a user interacts with an image, a navigation action directs them to the FullScreenScreen.

    In the FullScreenScreen, the image is enfolded by another Hero widget, bearing the same tag as the corresponding image in the ThumbnailScreen. This tag steers the animation as the image transits from the thumbnail screen to the full-screen display. Additionally, the image is nested within a GestureDetector, enabling users to tap anywhere on the screen to revert to the thumbnail screen.

    Fig 2: Hero Animation

    When a user taps an image on the ThumbnailScreen, Flutter orchestrates an animation that transports the shared element to the FullScreenScreen. Upon tapping the image in the FullScreenScreen, Flutter conducts an animation that returns the shared element to the ThumbnailScreen. The Hero widget proves invaluable in rendering seamless and captivating transitions between screens, and it’s particularly effective for enhancing user experiences in e-commerce applications.

    Implicit Animations

    Implicit animations are an invaluable tool for generating simple animations in Flutter that respond to changes in widget properties. Rather than delving into intricate animation controllers and tweens, implicit animations empower you to animate a widget’s properties without concerning yourself with the animation’s intricate details. One prime example of an implicit animation tool is the AnimatedContainerwidget.

    The AnimatedContainer widget is akin to a standard Container, but it boasts added animation capabilities. It animates alterations to properties such as size, color, and shape. Let’s explore an example illustrating the use of the AnimatedContainer widget to animate a color change when a button is pressed.

    Implicit Animation Example

    // Import required packages and libraries
    import 'package:flutter/material.dart';
    
    // Define the ImplicitAnimations widget
    class ImplicitAnimations extends StatefulWidget {
      const ImplicitAnimations({Key? key}) : super(key: key);
      @override
      createState() => ImplicitAnimationsState();
    }
    
    class ImplicitAnimationsState extends State<ImplicitAnimations> {
      bool _isPressed = false;
    
      void _togglePressed() {
        setState(() {
          _isPressed = !_isPressed;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Color and Position Change'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  AnimatedContainer(
                    duration: const Duration(seconds: 1),
                    height: 200.0,
                    width: 200.0,
                    margin: EdgeInsets.only(
                      top: _isPressed ? 70.0 : 0.0,
                    ),
                    decoration: BoxDecoration(
                      color: _isPressed ? Colors.blue : Colors.red,
                      borderRadius: BorderRadius.circular(_isPressed ? 50.0 : 0.0),
                    ),
                  ),
                  const SizedBox(height: 20.0),
                  ElevatedButton(
                    onPressed: _togglePressed,
                    child: const Text('ANIMATE'),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }

    In this example, the ImplicitAnimations widget houses an _isPressed boolean variable and a _togglePressed() function to toggle its value. The widget’s state class features a setState() call within _togglePressed() to trigger a widget tree rebuild when the value changes.

    The widget’s structure revolves around a Center widget positioned within a Scaffold. This Center widget accommodates a Columncontaining two main elements:

    1. AnimatedContainer: This widget animates its properties based on the _isPressed value. In this example, changes to height, width, margin, color, and border radius are animated.
    2. ElevatedButton: This button activates the animation when pressed, invoking the _togglePressed() function to toggle _isPressed.

    Implicit animations are a user-friendly method to introduce animations into your Flutter app without the intricacies of animation controllers and tweens.

    Fig 3: Implicit Animation

    Animated Vector Graphics

    Flutter’s capacity to create intricate and dynamic animations is further exemplified by its support for animated vector graphics through different tools. Vector-based graphics that maintain their quality when resized. Flutter utilises different tools to create animations and import them into the application.

    Rive

    Rive presents an avenue to craft animations that can be deployed across platforms, from mobile to the web. Rive animations are exported as Rive files, which can be integrated into your Flutter project via the Rive package. This package also supplies a Rive widget to showcase Rive animations within your app.

    Let’s delve into the process of implementing Rive in a Flutter app:

    // Import required packages and libraries
    import 'package:flutter/material.dart';
    import 'package:rive/rive.dart';
    
    // Define the RiveAnimations widget
    class RiveAnimations extends StatelessWidget {
      const RiveAnimations({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.grey,
          body: Center(
            child: Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(20),
                color: Colors.blue,
              ),
              width: 300,
              height: 300,
              child: const RiveAnimation.asset('assets/ball.riv'),
            ),
          ),
        );
      }
    }

    To incorporate animated vector graphics in your Flutter animations using Rive, follow these steps:

    1. Create the animation using the rive website and download it in the appropriate format.
    2. Import the Rive package into your Flutter project.
    3. Utilize the RiveAnimation.asset widget, providing the path to your Rive file.

    The animation can be controlled using the RiveAnimationController class, enabling you to start, stop, and pause the animation as needed.

    Fig 4: Rive animation

    The animation above was made in rive to illustrate a ball getting bounced and kicked but it doesn’t portray all the attributes of rive. Rive can be used to set different states for your animation, which makes your animation more dynamic to work with. You can make the animations react to states in your application, but you have to be good at animation to utilise this aspect of rive. In addition to Rive, you can also explore other tools like Lottie for incorporating pre-built animations into your Flutter app.

    Lottie Animations

    Lottie is a popular library that enables you to render animations in your app using JSON files generated using animation tools like Adobe After Effects. Lottie animations are vector-based, delivering a smooth and high-quality visual experience. In Flutter, you can integrate Lottie animations using the Lottie package, which offers the Lottie.asset and Lottie.network widgets.

    Here’s an example of how to create Lottie animations in Flutter:

    // Import required packages and libraries
    import 'package:flutter/material.dart';
    import 'package:lottie/lottie.dart';
    
    // Define the LottieAnimations widget
    class LottieAnimations extends StatelessWidget {
      const LottieAnimations({Key? key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const SizedBox(height: 50),
                SizedBox(
                  height: 200,
                  width: 200,
                  child: LottieBuilder.asset('assets/shopping.json'),
                ),
                const SizedBox(height: 50),
                const Text(
                  'add items to cart',
                  style: TextStyle(fontSize: 30),
                ),
              ],
            ),
          ),
        );
      }
    }

    Lottie animations bring your app to life with ease. Simply find or create a desired animation in Lottie format (JSON), import the Lottie package, and use the provided widgets to showcase your animation within your Flutter app.

    Fig 5: Lottie Animation

    Conclusion

    Animations play a pivotal role in mobile app development, enhancing user experiences by infusing apps with dynamism and engagement. This article has provided an in-depth exploration of Flutter’s animation capabilities, from its animation system and essential classes to advanced techniques such as physics-based animations, custom animations, and animated transitions using the Hero widget. We’ve also discussed implicit animations and integrating animated vector graphics using packages like Rive and Lottie.

    By leveraging these resources, you can further delve into the world of advanced Flutter animations, crafting captivating and dynamic user interfaces that set your mobile apps apart.

    2 thoughts on “A Comprehensive Guide to Advanced Animation Techniques in Flutter

    1. Excellent work here. I’ve learnt so much about flutter animations in such a nice way.

      Thank you for this wonderful piece.

    2. Thanks so much for this. Gonna try to implement every single one of this except for Rive in my next personal project

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    I'm a mobile developer with a passion for creating pixel-perfect products and interactive user experiences. Additionally, I excel in technical writing, breaking down complex topics into easily understandable bits for individuals at any level.
    Avatar
    Reviewed by:
    I picked up most of my soft/hardware troubleshooting skills in the US Army. A decade of Java development drove me to operations, scaling infrastructure to cope with the thundering herd. Engineering coach and CTO of Teleclinic.