Flutter Animation
In Flutter, animations are a powerful tool to enhance the user experience by providing smooth and visually appealing transitions or effects. Here’s a quick overview of Flutter animations and how to work with them:
In Flutter, animations do not handle the actual rendering of frames. Instead, the framework calculates the values for each frame and provides these to widgets for rendering. This ensures smooth and efficient animations without unnecessary computation. Flutter uses an Animation Controller to manage the timing of the animation and an Animation object to hold the values that change over time.
Animation in Mobile Applications
Animation is a core component in modern mobile applications, used to enhance the user experience by making apps feel more dynamic, engaging, and interactive. Though it adds complexity, it also adds a layer of sophistication that elevates the overall usability and appeal of an app. Flutter’s framework makes it relatively easy to implement animations, offering a powerful and intuitive API for building custom animations.
Animation is the process of creating the illusion of movement by displaying a sequence of images or visual changes in a particular order and within a specific duration. In a mobile app, animation typically involves animating UI elements such as buttons, images, and containers.
Start Value and End Value
- Every animation begins at a start value and progresses to an end value.
- These values define the animation’s range, such as size, opacity, position, etc.
- For example, to animate a widget’s opacity (make it fade), the animation starts at full opacity (1.0) and ends at no opacity (0.0).
Intermediate Values
- During the animation process, intermediate values are calculated to transition the widget smoothly from start to end.
- These values can follow a linear path (constant speed) or non-linear curves (e.g., ease-in, ease-out) depending on the animation type.
Duration
- The duration of an animation determines how fast or slow the transition happens.
- It can range from milliseconds to several seconds and directly affects the visual experience of the user.
Control Over Animation
Flutter provides great control over animations, allowing developers to:
- Start and stop animations.
- Repeat animations a specified number of times.
- Reverse the animation (e.g., play forwards and then backwards).
- These controls help developers create animations that match the behavior expected in the application.
Types of Animations in Flutter
1: Implicit Animations
Implicit animations are designed for simple animations that don’t require much code or manual control. You provide a starting and ending state, and Flutter automatically animates the transition between them.
Common Implicit Animations:- AnimatedContainer: Animates changes to properties of a container (e.g., width, height, color).
- AnimatedOpacity: Animates changes to opacity.
- AnimatedAlign: Animates alignment changes.
- AnimatedPositioned: Animates the position of a widget.
- AnimatedSwitcher: Automatically animates between different child widgets.
Example: AnimatedContainer
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Implicit Animation')),
body: AnimatedContainerExample(),
),
);
}
}
class AnimatedContainerExample extends StatefulWidget {
@override
_AnimatedContainerExampleState createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: AnimatedContainer(
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.blue : Colors.red,
),
),
);
}
}
Explicit Animations
Absolutely, let's dig deeper into explicit animations in Flutter. This animation type gives you full control over the animation's behavior, making it ideal for custom, intricate designs. Here's an in-depth breakdown of the components and how they work together:
- Used for more complex, custom animations.
- Requires managing animation controllers and tweens.
- Examples: AnimationController, Tween, AnimatedBuilder.
Example: Explicit Animation
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Explicit Animation Example')),
body: ExplicitAnimationDemo(),
),
);
}
}
class ExplicitAnimationDemo extends StatefulWidget {
@override
_ExplicitAnimationDemoState createState() => _ExplicitAnimationDemoState();
}
class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 50, end: 200).animate(
CurvedAnimation(parent: _controller, curve: Curves.bounceOut),
);
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.red,
);
},
),
);
}
}
Animation-Based Classes in Flutter
Flutter's animation system is built around Animation objects, which are the core components used to create dynamic, smooth animations. These classes provide flexibility to animate different types of properties, such as numbers, colors, sizes, and more. Below are the key animation classes used in Flutter and their typical usage:
1. Animation Class
An Animation is a class that generates interpolated values between two numbers over a specific duration. It’s used for most animations in Flutter and can interpolate values of different types, such as numbers, colors, or even sizes.
Common Types of Animation Classes:1: AnimationController
- This is the backbone of explicit animations.
- Controls the animation (start, stop, reverse, repeat).
- Works with a Ticker to update the animation's value frame by frame.
- Requires a vsync (usually a State class with SingleTickerProviderStateMixin).
AnimationController Initialization
AnimationController _controller = AnimationController(
duration: Duration(seconds: 2), // Total duration of the animation
vsync: this, // Handles frame updates efficiently
);
2: Tween
A Tween defines the range of values that will be interpolated during an animation. It connects the start and end values of the animation, while the AnimationController determines how the values change over time.
- Defines the range of values the animation will interpolate between.
- A Tween does not hold state; instead, it maps a controller's value (0.0 to 1.0) to a specific range.
- Example ranges: Tween
(begin: 50.0, end: 200.0).
- Tween<double>: For animating numeric values.
- ColorTween: For animating colors.
- SizeTween: For animating size values.
- Animation<double>: Interpolates between two double values (e.g., scaling a widget, animating opacity).
Animation<double> animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
Animation<Color> colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(controller);
Animation<Size> sizeAnimation = SizeTween(begin: Size(100, 100), end: Size(200, 200)).animate(controller);
Animation Initialization with Tween
Animation<double> _animation = Tween<double>(
begin: 50,
end: 200
).animate(_controller);
3: Curves
A CurvedAnimation is used to apply non-linear timing to the animation. Instead of the animation running linearly from the start to the end, the curve allows you to control the speed of the animation over time (e.g., easing in or out).
- Adds smooth transitions to animations by controlling the rate of change.
- .Examples: Curves.easeIn, Curves.easeOut, Curves.bounceIn.
Animation Initialization with CurvedAnimation
Animation<double> _curvedAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
4: AnimatedBuilder
AnimatedBuilder is a widget that rebuilds parts of the widget tree when the animation changes. This is especially useful for optimizing performance, as it only rebuilds the widgets that are dependent on the animation values.
- A widget that rebuilds its child during the animation, making it efficient.
- Avoids rebuilding the entire widget tree.
AnimatedBuilder with Animation
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
);
},
);
6. AnimationStatusListener Class
An AnimationStatusListener can be used to listen for changes in the status of the animation, such as when it starts, stops, completes, or repeats. This can help in triggering actions based on animation progress.
Adding a Status Listener to the Controller
Example:
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// Do something when the animation completes
}
});
Workflow of Flutter Animation
The animation workflow in Flutter is a systematic process where different stages allow developers to control and apply animations to UI components. Below are the steps involved in setting up and running an animation in Flutter:
1. Define and Start the Animation Controller
The first step in creating an animation is to define and initialize an AnimationController. This controller manages the timing of the animation and ensures smooth transitions over the specified duration. It needs to be initialized in the initState() method of a StatefulWidget.
Example
@override
void initState() {
super.initState();
// Define the animation controller with a duration
controller = AnimationController(
duration: const Duration(seconds: 2), // Duration of the animation
vsync: this, // Ensures the animation is synchronized with the screen refresh rate
);
// Create an animation by specifying the range of values (from 0 to 300)
animation = Tween<double>(begin: 0, end: 300).animate(controller);
// Start the animation forward
controller.forward();
}
Example
Add Animation Listener
Once the animation is created, you can add a listener to detect changes in the animation’s value. The addListener() method allows the widget’s state to be updated whenever the animation progresses. This ensures the widget rebuilds and reflects the current animation value.
@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
setState(() {
// The state change here is triggered by the animation value
});
});
controller.forward(); // Start the animation
}
The setState() method is called inside the listener to update the widget's state every time the animation value changes, ensuring that the UI reflects the new values.
Use Built-in Widgets to Simplify Animation
Flutter provides built-in widgets like AnimatedWidget and AnimatedBuilder that help simplify the animation process. These widgets automatically listen for animation changes and rebuild the widget tree based on the animation's value, saving you from manually handling listeners.
- AnimatedWidget: Automatically rebuilds itself when the animation changes.
- AnimatedBuilder Rebuilds only the relevant part of the widget tree, improving performance.
Apply Animation Values in the Build Method
Once the animation is set up and running, you can access its value inside the build() method of the widget. This value will be used to dynamically adjust properties like width, height, color, or any other animated attribute.
Example
@override
Widget build(BuildContext context) {
return Container(
height: animation.value, // Apply the animation value to height
width: animation.value, // Apply the animation value to width
child: Center(
child: Text('Animated Box'),
),
);
}
Working Application
Let’s create a simple Flutter application that demonstrates animation by animating a widget. The application will display a product list, and the content will animate using a Tween.
Step 1: Create a New Flutter Application
- Open Android Studio and create a new Flutter project named
product_animation_app.
Step 2: Add Assets
- Copy the
assets
folder containing product images (e.g., floppy, iPhone, laptop, pendrive, etc.) to the root directory of your Flutter project. - Add the assets to the
pubspec.yaml
file:
Flutter Assets:
- assets/images/yourSecondImage.jpg
- assets/images/yourThirdtImage.jpg
- assets/images/yourFourthImage.jpg
- assets/images/yourFiveImage.jpg
- assets/images/sixImage.jpg
Step 3: Create the Main Dart File
- Open the
lib/main.dart
file and remove the default code. - Add the following import and
main()
function.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
Step 4: Create the MyApp Widget
Define the MyApp widget, extending StatefulWidget:
Example: Stateful Widget
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
Step 5: Create the MyAppState
Implement initState, dispose, and build methods within _MyAppState.
class _MyAppState extends State MyApp> with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: const Duration(seconds: 5))
..forward();
animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Product Animation Demo', animation: animation),
);
}
}
Step 6: Create the MyHomePage Widget
Define the MyHomePage widget and accept the animation object via the constructor:
class MyHomePage extends StatelessWidget {
final String title;
final Animation<double> animation;
MyHomePage({Key? key, required this.title, required this.animation}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FadeTransition(
opacity: animation, // Use animation for fading effect
child: ListView(
children: <Widget>[
ProductBox(
name: "iPone90",
description: "Apple iPhone90",
price: 1000,
image: "Nokia.jpg",
),
ProductBox(
name: "Pixel",
description: "Google Pixel",
price: 800,
image: "pixel.jpg",
),
ProductBox(
name: "Laptop",
description: "Dell Laptop",
price: 1500,
image: "laptop.jpg",
),
ProductBox(
name: "Tablet",
description: "Iphone Tablet",
price: 600,
image: "tablet.jpg",
),
ProductBox(
name: "Pendrive",
description: "16GB USB",
price: 20,
image: "laptop.jpg",
),
ProductBox(
name: "Floppy",
description: "Vintage Floppy Disk",
price: 5,
image: "book.jpg",
),
],
),
),
);
}
}
Step 7: Add the ProductBox Widget
Create the ProductBox widget to display product details:
class ProductBox extends StatelessWidget {
final String name;
final String description;
final int price;
final String image;
ProductBox({Key? key, required this.name, required this.description, required this.price, required this.image}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image), // Product Image
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
name,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(description),
Text("Price: \$" + price.toString()),
],
),
),
),
],
),
),
);
}
}
Step 8: Run the Application
Run the app using the command:
Flutter Assets:
The app will display a list of products, with the content animating smoothly due to the fade effect provided by the animation.
MyAnimatedWidget
class CustomAnimatedWidget extends StatelessWidget {
CustomAnimatedWidget({Key? key, required this.child, required this.opacityAnimation})
: super(key: key);
final Widget child;
final Animation<double> opacityAnimation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: opacityAnimation,
builder: (context, widgetChild) => Container(
child: Opacity(
opacity: opacityAnimation.value,
child: widgetChild,
),
),
child: child,
),
);
}
}
MyHomePage
class CustomHomePage extends StatelessWidget {
CustomHomePage({Key? key, this.header, this.fadeAnimation})
: super(key: key);
final String? header;
final Animation<double>? fadeAnimation;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(header ?? "Product Listing"),
),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
FadeTransition(
opacity: fadeAnimation!,
child: ProductBox(
name: "Samsung",
description: "Samsung has great camera phones.",
price: 1200,
image: "samsung.jpg",
),
),
CustomAnimatedWidget(
child: ProductBox(
name: "MacBook",
description: "MacBook is the best laptop for creatives.",
price: 2500,
image: "macbook.jpg",
),
opacityAnimation: fadeAnimation!,
),
ProductBox(
name: "Headphones",
description: "Headphones for immersive sound.",
price: 300,
image: "headphones.jpg",
),
ProductBox(
name: "Smartwatch",
description: "Smartwatch for fitness and notifications.",
price: 200,
image: "smartwatch.jpg",
),
ProductBox(
name: "Power Bank",
description: "Power Bank for charging on the go.",
price: 50,
image: "powerbank.jpg",
),
ProductBox(
name: "Router",
description: "Router for fast and reliable WiFi.",
price: 100,
image: "router.jpg",
),
],
),
);
}
}
The complete code is as follows −
import 'package:flutter/material.dart';
void main() => runApp(ProductApp());
class ProductApp extends StatefulWidget {
@override
_ProductAppState createState() => _ProductAppState();
}
class _ProductAppState extends State<ProductApp> with SingleTickerProviderStateMixin {
Animation<double> fadeEffect;
AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: const Duration(seconds: 10), vsync: this);
fadeEffect = Tween<double>(begin: 0.0, end: 1.0).animate(animationController);
animationController.forward();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Product App',
theme: ThemeData(primarySwatch: Colors.blue),
home: ProductHomePage(
title: 'Product Listing',
fadeAnimation: fadeEffect,
)
);
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
}
class ProductHomePage extends StatelessWidget {
ProductHomePage({Key? key, this.title, this.fadeAnimation}): super(key: key);
final String? title;
final Animation<double>? fadeAnimation;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title ?? 'Product Listing')),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
FadeTransition(
child: ProductCard(
productName: 'iPhone',
productDescription: 'iPhone is the most stylish phone ever',
productPrice: 1000,
productImage: 'iphone.jpg',
),
opacity: fadeAnimation!,
),
AnimatedProduct(
child: ProductCard(
productName: 'Pixel',
productDescription: 'Pixel is the most feature-rich phone ever',
productPrice: 800,
productImage: 'pixel.jpg',
),
fadeAnimation: fadeAnimation!,
),
...
],
),
);
}
}
class ProductCard extends StatelessWidget {
ProductCard({Key? key, this.productName, this.productDescription, this.productPrice, this.productImage})
: super(key: key);
final String? productName;
final String? productDescription;
final int? productPrice;
final String? productImage;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 140,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset('assets/appimages/' + productImage!),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(productName!, style: TextStyle(fontWeight: FontWeight.bold)),
Text(productDescription!),
Text('Price: ' + productPrice.toString()),
],
),
),
),
],
),
),
);
}
}
class AnimatedProduct extends StatelessWidget {
AnimatedProduct({this.child, this.fadeAnimation});
final Widget? child;
final Animation<double>? fadeAnimation;
@override
Widget build(BuildContext context) => Center(
child: AnimatedBuilder(
animation: fadeAnimation!,
builder: (context, animatedChild) => Container(
child: Opacity(opacity: fadeAnimation!.value, child: animatedChild),
),
child: child,
),
);
}