Advanced Animation Techniques for Flutter: A Guide
Animations play a crucial role in enhancing the user experience and improving an application’s UI’s overall look and feel. They help convey changes, transitions, and interactions more engagingly and intuitively. They can make your app feel more responsive, provide visual feedback to user actions, guide users’ attention, and create a more polished and professional appearance. This article will show you ways to create great animations in Flutter.
Discover how at OpenReplay.com.
Flutter is an open-source UI software development kit created by Google. It is used to develop applications for Android, iOS, Linux, Mac, Windows, and the web from a single codebase. Flutter uses the Dart programming language and follows a reactive programming paradigm. It provides a rich set of pre-built and customizable widgets and a powerful framework for building beautiful, fluid User Interfaces(UI).
Animations in Flutter
In Flutter, animations are controlled by an AnimationController
object, which manages the animation’s duration, status (e.g., playing, paused, completed), and forward or reverse playback. The AnimationController
is connected to an Animation object, which generates a series of values over time (e.g., numbers, colors, sizes) that can be used to update the UI.
Below are some key classes that are frequently used in creating animations in Flutter:
- Tween: A Tween defines a range of values for
interpolation
in an animation. For example, aTween<double>
can define a range of double values, while aTween<Color>
can define a range of colors. - AnimationController: AnimationController controls the animation process, including its duration, status, and playback direction. It is typically used with an
Animation
object to update the UI based on the animation’s current value. - AnimatedBuilder: An AnimatedBuilder widget is used to rebuild itself whenever the animation changes. It is useful for encapsulating complex UI animations that require updating multiple widgets.
- Curve: A Curve is used to dictate an animation’s progression rate. Flutter provides several built-in curve types, such as
Curves.linear
,Curves.easeIn
,Curves.easeOut
, andCurves.easeInOut
, among others.
Setting Up Animations In Flutter
To set up animations in Flutter, you typically create an AnimationController
and configure it with duration and optional settings such as initial value, lower bound
, and upper bound
. You then create an Animation
object by chaining the AnimationController
with a Tween
or Curve
, which defines the range of values or the progression rate of the animation. Finally, you can use the addListener method to update the UI based on the animation’s current value.
Here’s an example of how you can create an AnimationController
for a simple fade-in animation:
import 'package:flutter/material.dart';
// The entry point for the Flutter app
void main() {
// Run the app by passing the MyApp widget to runApp
runApp(MyApp());
}
// MyApp is a stateless widget that returns MaterialApp
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// MaterialApp is the root of your application
return MaterialApp(
// MyHomePage is set as the home page of the app
home: MyHomePage(),
);
}
}
// MyHomePage is a StatefulWidget
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
// _MyHomePageState is the State class for MyHomePage class
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
// Declare an AnimationController
late AnimationController _controller;
// Declare an Animation
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Initialize the AnimationController with a duration of 2 seconds
// and the 'vsync' parameter set to 'this' for performance reasons
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Create an animation that goes from 0.0 to 300.0
_animation = Tween(begin: 0.0, end: 300.0).animate(_controller);
// Add a listener to the animation to update the UI when the animation value changes
_controller.addListener(() {
setState(() {});
});
// Start the animation
_controller.forward();
}
@override
void dispose() {
// Dispose the AnimationController when the widget is removed from the widget tree
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Scaffold widget provides a default app bar, title, and body
return Scaffold(
appBar: AppBar(
title: Text('Animation Examples'),
),
// Center widget centers its child widget
body: Center(
// Container widget is a box model that can have width, height, and color
child: Container(
// Set the width and height of the container to the current value of the animation
width: _animation.value,
height: _animation.value,
color: Colors.blue, // Set the color of the container to blue
),
),
);
}
}
In this example, we’re using an AnimationController
to animate the size of a Container widget. The AnimationController
is initialized in the initState method, and the animation’s value is updated using a Tween
and a listener. The dispose method is used to clean up the animation controller when the widget is removed from the widget tree.
This GIF demonstrates a simple animation in Flutter using an AnimationController
. The blue square smoothly transitions from a small size to a larger size over 2 seconds.
Physics-Based Animations
Flutter provides a Simulation class that allows you to create Physics-based animations, such as spring animations, by defining the animation’s behavior based on physical properties like mass, stiffness, and damping.
Let’s explore a physics-based animation example in Flutter called spring animation. By leveraging the SpringSimulation
class, we can simulate an oscillator, which allows us to achieve a realistic spring-like effect in our animations.
Here’s how you can create a spring animation using SpringSimulation.
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(MaterialApp(
home: PhysicsSpringAnimationExample(),
));
}
class PhysicsSpringAnimationExample extends StatefulWidget {
@override
_PhysicsSpringAnimationExampleState createState() =>
_PhysicsSpringAnimationExampleState();
}
class _PhysicsSpringAnimationExampleState
extends State<PhysicsSpringAnimationExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Initialize the AnimationController
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500), // Animation duration
);
// Define the properties of the spring animation
final SpringDescription _spring = SpringDescription(
mass: 1, // Mass of the object
stiffness: 100, // Stiffness of the spring
damping: 10, // Damping of the spring
);
// Create a SpringSimulation for the spring animation
final SpringSimulation _springSimulation =
SpringSimulation(_spring, 0.0, 1.0, 0.5); // Starting position, end position, and velocity
// Drive the animation using the SpringSimulation
_animation = _controller.drive(
Tween<double>(
begin: _springSimulation.x(0), // Starting position
end: _springSimulation.x(1), // End position
),
);
// Start the animation
_controller.animateWith(_springSimulation);
}
@override
void dispose() {
// Dispose the AnimationController when done
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Spring Animation Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// AnimatedBuilder to rebuild the widget when the animation updates
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
// Transform the widget using the scale value from the animation
return Transform.scale(
scale: _animation.value,
child: Container(
width: 100, // Adjust size of animation
height: 100, // Adjust size of animation
color: Colors.blue,
),
);
},
),
SizedBox(height: 20),
// Button to restart the animation
ElevatedButton(
onPressed: () {
_controller.reset(); // Reset the animation
_controller.forward(); // Start the animation
},
child: Text('Start Animation'),
),
],
),
),
);
}
}
This code sets up a Flutter app with a spring animation effect. When you run the app, a blue square will appear, animating as if it were attached to a spring. Pressing the ‘Start Animation’ button triggers the animation, causing the square to stretch and compress accordingly. The animation is controlled by an AnimationController
and driven by a SpringSimulation
, which defines the physics of the spring animation.
This GIF demonstrates a Flutter app with a spring animation effect. The animation shows a blue square that behaves as if it were attached to a spring, stretching and compressing based on the physics of an oscillator. Pressing the ‘Start Animation’ button triggers the animation, showcasing Flutter’s capabilities.
Hero Animation
The Hero widget in Flutter is used for shared element transitions, where an element (e.g., an image, or text) smoothly transitions from one screen to another. The Hero
widget animates the shared element’s size, position, and opacity to create a seamless transition effect.
Implementation Example For Smooth Image Transitions
Let’s consider a simple example of a Hero
animation using two screens: FirstScreen
and SecondScreen
. In FirstScreen
, we have an image wrapped in a Hero
widget. When tapped, the image navigates to SecondScreen
, where the same image is displayed with a smooth animation effect.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: FirstScreen(),
));
}
// First screen with a Hero animation
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: GestureDetector(
onTap: () {
// Navigate to the second screen when the image is tapped
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(),
),
);
},
child: Hero(
// Tag for the Hero animation, should be unique across screens
tag: 'imageHero',
// Image to be displayed in the Hero animation
child: Image.network(
"https://cdn.britannica.com/98/235798-050-3C3BA15D/Hamburger-and-french-fries-paper-box.jpg",
// Make the image fill the width of the screen
width: MediaQuery.of(context).size.width,
// Set a fixed height for the image
height: 200,
// Cover the entire image with no distortion
fit: BoxFit.cover,
),
),
),
);
}
}
// Second screen with a Hero animation
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: GestureDetector(
onTap: () {
// Pop the current screen off the navigation stack when tapped
Navigator.pop(context);
},
child: Hero(
// Tag for the Hero animation, should be the same as in the first screen
tag: 'imageHero',
// Image to be displayed in the Hero animation
child: Image.network(
"https://www.aheadofthyme.com/wp-content/uploads/2021/11/veggie-tray-2.jpg",
// Make the image fill the width of the screen
width: MediaQuery.of(context).size.width,
// Set a fixed height for the image
height: 200,
// Cover the entire image with no distortion
fit: BoxFit.cover,
),
),
),
);
}
}
This code demonstrates how to use the Hero
widget in Flutter to create a shared element transition between two screens. On the first screen, an image is displayed with a Hero
widget. When the image is tapped, the app navigates to the second screen where the same image is displayed with a Hero
widget
. The tag
property of the Hero
widget is used to identify the same image across screens and animate its transition.
This GIF demonstrates a Hero
widget in action with this Flutter app. The animation smoothly transitions an image from the first screen to the second, creating a visually appealing effect. The imageHero
tag ensures the images match up perfectly for a seamless transition.
Implicit Animations
Implicit animations in Flutter are animations that are automatically applied to widgets when their properties change. These animations are triggered by changes in the widget’s properties, such as size, position, or color, and do not require explicit animation controllers or builders.
The AnimatedContainer widget in Flutter is an example of an implicit animation. When the properties of the AnimatedContainer
(e.g., width, height, color) change, Flutter automatically animates the transition between the old and new values, making smooth animation without the need for explicit code.
Implicit Animation Example
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MaterialApp(
home: ImplicitAnimationExample(),
));
}
class ImplicitAnimationExample extends StatefulWidget {
@override
_ImplicitAnimationExampleState createState() =>
_ImplicitAnimationExampleState();
}
class _ImplicitAnimationExampleState extends State<ImplicitAnimationExample> {
// Initial values for width, height, and color
double _width = 100.0;
double _height = 100.0;
Color _color = Colors.blue;
// Method to change the properties of the container
void _changeProperties() {
setState(() {
final random = Random();
_width = random.nextInt(300).toDouble(); // Random width
_height = random.nextInt(300).toDouble(); // Random height
_color = Color.fromARGB(
255,
random.nextInt(256), // Random red value
random.nextInt(256), // Random green value
random.nextInt(256), // Random blue value
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Implicit Animation Example'),
),
body: Center(
child: GestureDetector(
onTap: _changeProperties, // Call _changeProperties on tap
child: AnimatedContainer(
duration: Duration(seconds: 1), // Animation duration
curve: Curves.fastOutSlowIn, // Easing curve for the animation
width: _width, // Animated width
height: _height, // Animated height
color: _color, // Animated color
child: Center(
child: Text(
'Tap to change',
style: TextStyle(color: Colors.white),
),
),
),
),
),
);
}
}
This code creates a simple Flutter app with an AnimatedContainer
widget that animates changes to its width, height, and color properties. Tapping on the container triggers the animation, randomly changing its size and color. The animation duration is set to 1 second, and the easing curve used is Curves.fastOutSlowIn
Flutter app demonstrating implicit animation with AnimatedContainer
. The container changes size and color automatically, showcasing Flutter’s smooth animation capabilities.
Animated Vector Graphics
Animated vector graphics (AVG) are graphics that use vector shapes and paths to create animations. In Flutter, AVG can be implemented using libraries like Rive and Lottie, which allow you to import vector graphics animations created in design tools like Adobe After Effects and Adobe Illustrator.
Rive
Rive is a powerful design and animation tool that allows you to create stunning animations and vector graphics for use in your Flutter applications. With Rive, you can easily design complex animations and export them to various formats for integration into your projects.
Here’s a basic example of how you can integrate a Rive animation into your Flutter app:
import 'package:flutter/material.dart';
import 'package:rive/rive.dart'; // Import Rive package
void main() {
runApp(MaterialApp(
home: RiveAnimationExample(),
));
}
class RiveAnimationExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Rive Animation Example'),
),
body: Stack(
children: [
// Display a Rive animation from an asset file
RiveAnimation.asset(
"assets/shapes.riv", // Path to the Rive animation asset
fit: BoxFit.cover, // Fit the animation to the available space
),
],
),
);
}
}
This code sets up a Flutter app with a Rive animation effect. When you run the app, you’ll see the Rive animation on the screen. The RiveAnimation.asset widget
is used to load and display the Rive animation from a file
To incorporate animated vector graphics in your Flutter animations using Rive, follow these steps:
- Create the animation using the Rive website and download it in the appropriate format.
- Import the Rive package into your Flutter project.
- Utilize the
RiveAnimation.asset
widget, providing the path to your Rive file.
This code sets up a Flutter app that displays a Rive animation consisting of bouncing shapes (circle, triangle, and hexagon) on the screen. The RiveAnimation.asset
widget is used to load and display the Rive animation from the assets/shapes.riv
file. The animation is overlaid on the screen using a Stack widget, allowing multiple widgets to be displayed on top of each other
Lottie Animations
Lottie is an open-source animation file format that’s lightweight and easy to use. It’s designed to render vector animations and graphics in a way that’s scalable and resolution-independent. Lottie animations are created using Adobe After Effects and exported using the Bodymovin plugin. These animations can be easily integrated into mobile, web, and desktop applications.
In Flutter, the Lottie library allows developers to add Lottie animations to their apps. This library provides widgets for loading and displaying Lottie animations, making it simple to include complex animations in Flutter apps without sacrificing performance. Lottie animations can enhance the user experience by adding visually appealing and engaging graphics to your app.
Here’s an example of how to create Lottie animations in Flutter:
import 'package:flutter/material.dart'; // Import the Flutter material library
import 'package:lottie/lottie.dart'; // Import the Lottie library for animations
void main() {
runApp(MaterialApp(
home: LottieAnimationExample(), // Set the home screen to LottieAnimationExample
));
}
class LottieAnimationExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lottie Animation Example'), // Set the title of the app bar
),
body: Center(
// Center aligns its child widget
child: Lottie.asset(
'assets/lottie.json', // Path to the Lottie animation asset
width: 200, // Set the width of the animation
height: 200, // Set the height of the animation
fit: BoxFit.cover, // Fit the animation to cover the entire space
),
),
);
}
}
This Flutter code sets up an app that displays a Lottie animation. The Lottie.asset
widget loads and displays the animation from a JSON file, and the app bar (AppBar
) provides a title for the app. When the app is run, the animation will be centered on the screen and cover the entire space available to it.
Bring your app to life with Lottie! This powerful library allows you to easily add high-quality animations to your Flutter app. Simply import your animation in Lottie format (JSON), use the Lottie package, and showcase your animations with ease.
Conclusion
In conclusion, Flutter’s comprehensive animation framework offers developers a wide array of tools and APIs to create captivating user experiences. By mastering these techniques, developers can seamlessly integrate animations into their apps, enhancing user engagement and overall app quality. From implicit animations to physics-based simulations and shared element transitions, Flutter provides the flexibility and scalability needed to bring apps to life.
As Flutter continues to evolve, developers are encouraged to explore these advanced animation techniques further, pushing the boundaries of what’s possible in mobile app development. By leveraging Flutter’s animation capabilities, developers can deliver apps that not only meet but exceed user expectations, setting new standards for user interface design and interaction