Implementing Advanced Animations
1. Using AnimatedBuilder Widget
| Property | Description | Values |
|---|---|---|
| child | Child widget to be built | Widget? |
| animation | Animation controller driving the animation | Animation<double> |
| builder | Function that builds the widget | Widget Function(BuildContext, Widget?) |
Example: Basic AnimatedBuilder Usage
class AnimatedBuilderExample extends StatefulWidget {
@override
_AnimatedBuilderExampleState createState() => _AnimatedBuilderExampleState();
}
class _AnimatedBuilderExampleState extends State<AnimatedBuilderExample> with TickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
animation = Tween<double>(begin: 0, end: 1).animate(controller);
controller.repeat(reverse: true);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
child: Icon(Icons.favorite, size: 50),
builder: (context, child) {
return Transform.rotate(
angle: animation.value * 2 * pi,
child: child,
);
},
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
AnimatedBuilder is more efficient than rebuilding the entire widget tree when only a part of it needs to change.
2. Creating Staggered Animations
Staggered animations apply delays to sequential animation effects, creating a cascading or ripple-like visual effect. This is commonly used in list item animations or UI component reveal sequences.
| Property | Description | Values |
|---|---|---|
| delay | Time in milliseconds before the animation starts | int |
| duration | Total duration of the animation in milliseconds | int |
Example: Staggered Fade Animation
class StaggeredFadeAnimation extends StatefulWidget {
@override
_StaggeredFadeAnimationState createState() => _StaggeredFadeAnimationState();
}
class _StaggeredFadeAnimationState extends State<StaggeredFadeAnimation> with TickerProviderStateMixin {
late AnimationController controller;
late List<AnimationController> controllers;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 1000),
vsync: this,
);
controllers = List.generate(5, (index) => AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
));
}
@override
void dispose() {
controller.dispose();
for (var c in controllers) c.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(5, (index) {
final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: controllers[index], curve: Curves.easeIn),
);
return FadeTransition(
opacity: animation,
child: Container(
height: 50,
margin: EdgeInsets.symmetric(vertical: 5),
color: Colors.blue,
),
);
}),
);
}
}
CurvedAnimation to apply easing curves to staggered animations for smoother transitions.
Staggered Animation Best Practices
- Use
AnimationControllerwith individual delays for each widget. - Apply
CurvedAnimationto enhance visual flow. - Limit the number of staggered elements to prevent performance issues.
3. Using AnimatedWidget Class
The AnimatedWidget is a powerful base class in Flutter that simplifies the creation of animations by automatically handling the animation loop and providing a clean way to rebuild widgets when animation values change.
| Property | Description | Usage |
|---|---|---|
| child | The child widget to be animated. | Optional, but commonly used for passing in a widget that needs animation. |
| animation | The Animation<double> object that controls the animation. |
Mandatory. Must be provided to drive the animation state. |
Example: Basic AnimatedWidget Usage
import 'package:flutter/material.dart';
class AnimatedCircle extends AnimatedWidget {
AnimatedCircle({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable as Animation<double>;
return Container(
width: 100 * animation.value,
height: 100 * animation.value,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
);
}
}
class AnimationExample extends StatefulWidget {
@override
_AnimationExampleState createState() => _AnimationExampleState();
}
class _AnimationExampleState extends State<AnimationExample> with TickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
controller.repeat(reverse: true);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("AnimatedWidget Example")),
body: Center(
child: AnimatedCircle(animation: animation),
),
);
}
}
AnimatedWidget is ideal for animating a single widget that depends on one or more animations. It's particularly useful when you want to encapsulate animation logic into a reusable component.
4. Creating Custom Animated Widgets
| Property | Description | Example |
|---|---|---|
| child | The widget to be animated | Text('Animated Text') |
| duration | Duration of the animation in milliseconds | Duration(milliseconds: 500) |
| curve | The animation curve to use (e.g., Curves.easeInOut) |
Curves.easeInOut |
Example: Custom Animated Widget
class CustomAnimatedWidget extends AnimatedWidget {
final Animation<double> animation;
CustomAnimatedWidget({required this.animation}) : super(listenable: animation);
@override
Widget build(BuildContext context) {
return Transform.scale(
scale: animation.value,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
);
}
}
Example: Using Custom Animated Widget
class AnimationExample extends StatefulWidget {
@override
_AnimationExampleState createState() => _AnimationExampleState();
}
class _AnimationExampleState extends State<AnimationExample>
with TickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
animation = Tween<double>(begin: 1.0, end: 2.0).animate(
CurvedAnimation(parent: controller, curve: Curves.easeInOut),
);
controller.repeat(reverse: true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Custom Animated Widget')),
body: Center(
child: CustomAnimatedWidget(animation: animation),
),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
AnimatedWidget is a powerful way to create custom animated widgets that can be reused across your app.
5. Implementing Physics-Based Animations
Physics-based animations in Flutter simulate real-world physics to create natural, dynamic motion. These animations use the physics package or custom physics engines to model forces like gravity, friction, and elasticity.
| Property | Description | Values |
|---|---|---|
| spring | Defines spring physics properties for animation | SpringDescription |
| gravity | Gravity force applied to animated objects | double |
| friction | Resistance to motion over time | double |
| elasticity | How much bounce occurs on impact | double |
Example: Spring Animation
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: AnimatedBuilder(
animation: springAnimation,
builder: (context, child) {
return Transform.translate(
offset: Offset(springAnimation.value, 0),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
);
},
),
),
),
);
}
}
final springAnimation = Tween<double>(begin: 0, end: 200).animate(
CurvedAnimation(parent: AnimationController(vsync: MyHomePage(), duration: Duration(seconds: 2)), curve: Curves.elasticOut),
);
Example: Gravity Simulation
import 'package:flutter/material.dart';
class GravityAnimation extends StatefulWidget {
@override
_GravityAnimationState createState() => _GravityAnimationState();
}
class _GravityAnimationState extends State<GravityAnimation> with TickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 5),
);
animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(parent: controller, curve: Curves.easeOut),
);
controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, animation.value),
child: Container(
width: 50,
height: 50,
color: Colors.red,
),
);
},
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
AnimationController and CurvedAnimation to control timing and easing functions.
Key Concepts
- Spring Physics: Simulates spring-like behavior with customizable stiffness and damping.
- Gravity: Applies downward force to simulate real-world gravitational pull.
- Friction: Reduces velocity over time to simulate resistance in motion.
- Elasticity: Controls how much bounce occurs when objects collide.
6. Using AnimatedCrossFade Widget
The AnimatedCrossFade widget is a powerful tool for animating between two child widgets, smoothly transitioning from one to the other. It's particularly useful when you want to show different content based on a condition, such as switching between a loading indicator and the actual content.
| Property | Description | Values |
|---|---|---|
| firstChild | The widget to show when firstCurve is active. |
Widget |
| secondChild | The widget to show when secondCurve is active. |
Widget |
| firstCurve | The curve to use for animating from the first child to the second. | AnimatedSwitcherTransitionDelegate |
| secondCurve | The curve to use for animating from the second child to the first. | AnimatedSwitcherTransitionDelegate |
| duration | The duration of the animation. | Duration |
| layoutBuilder | Customizes how the widgets are laid out during the animation. | AnimatedCrossFadeLayoutBuilder |
Example: Basic usage of AnimatedCrossFade
class AnimatedCrossFadeExample extends StatefulWidget {
@override
_AnimatedCrossFadeExampleState createState() => _AnimatedCrossFadeExampleState();
}
class _AnimatedCrossFadeExampleState extends State<AnimatedCrossFadeExample> {
bool showFirst = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedCrossFade Example')),
body: Center(
child: AnimatedCrossFade(
firstChild: Container(
height: 200,
width: 200,
color: Colors.red,
child: Center(child: Text('First Widget')),
),
secondChild: Container(
height: 200,
width: 200,
color: Colors.blue,
child: Center(child: Text('Second Widget')),
),
crossFadeState: showFirst
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: Duration(seconds: 1),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
showFirst = !showFirst;
});
},
child: Icon(Icons.refresh),
),
);
}
}
AnimatedCrossFade widget is ideal for scenarios where you want to animate between two different widgets, such as switching between a loading indicator and the actual content.
7. Using AnimatedSwitcher Widget
| Property | Values | Description |
|---|---|---|
| child | Widget |
The widget to be animated when switched. |
| transitionBuilder | (Widget child, Animation<double> animation) |
Builder function to define how the child widget transitions. |
| duration | Duration |
Duration of the animation (default is 300ms). |
| reverseDuration | Duration |
Duration of the reverse animation (default is 300ms). |
Example: Basic AnimatedSwitcher
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: Text(
'Hello World',
key: ValueKey('hello'),
),
)
Example: Switching Between Widgets
AnimatedSwitcher(
duration: Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: child,
);
},
child: isShowingText
? Text('Flutter is awesome!', key: ValueKey('text'))
: Text('Hello Flutter!', key: ValueKey('hello')),
)
AnimatedSwitcher widget requires a unique key for each child to properly detect changes and animate transitions.
8. Using Hero Animations
Hero animations in Flutter provide a seamless transition between screens, making the user experience more fluid and engaging. They are particularly useful when navigating from a list screen to a detail screen, where an element appears to "fly" between the two screens.
| Property | Description | Values |
|---|---|---|
| tag | Unique identifier for the Hero animation. | Object |
| child | The widget to animate between screens. | Widget |
| transitionDuration | Duration of the animation transition. | Duration |
Example: Basic Hero Animation
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HeroListScreen(),
);
}
}
class HeroListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Hero List")),
body: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text("Item $index"),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HeroDetailScreen(item: index),
),
);
},
);
},
),
);
}
}
class HeroDetailScreen extends StatelessWidget {
final int item;
HeroDetailScreen({required this.item});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Item Detail")),
body: Center(
child: Hero(
tag: item,
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text(
"Item $item",
style: TextStyle(fontSize: 24),
),
),
),
),
),
);
}
}
tag must be unique across the app to ensure proper animation matching.
9. Using AnimatedList Widget
The AnimatedList widget in Flutter is a powerful tool for animating lists with dynamic content. It provides built-in animations for insertions, removals, and reordering of list items.
| Property | Description | Values |
|---|---|---|
| key | Unique identifier for the widget | Key? |
| itemBuilder | Builder function to create list items | IndexedWidgetBuilder |
| initialItemCount | Initial number of items in the list | int |
| scrollDirection | Direction of scrolling | Axis.horizontal or Axis.vertical |
| reverse | Reverse the scroll direction | bool |
| controller | Scroll controller to manage scrolling | ScrollController? |
| primary | Whether the list is the primary scrollable | bool? |
| physics | Scroll physics to control scrolling behavior | ScrollPhysics? |
Example: Adding Items to AnimatedList
class AnimatedListExample extends StatefulWidget {
@override
_AnimatedListExampleState createState() => _AnimatedListExampleState();
}
class _AnimatedListExampleState extends State<AnimatedListExample> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
List<String> _items = [];
void _addItem() {
final newItem = 'Item ${_items.length + 1}';
_items.insert(0, newItem);
_listKey.currentState!.insertItem(0);
}
Widget _buildItem(BuildContext context, int index) {
return ListTile(
title: Text(_items[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
final removedItem = _items.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) => _buildRemovedItem(removedItem, animation),
);
},
),
);
}
Widget _buildRemovedItem(String item, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(begin: Offset.zero, end: Offset(0, -1))
.animate(animation),
child: ListTile(
title: Text(item),
subtitle: Text('Removed'),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AnimatedList Example')),
body: AnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: _buildItem,
),
floatingActionButton: FloatingActionButton(
onPressed: _addItem,
child: Icon(Icons.add),
),
);
}
}
AnimatedList widget requires an AnimatedListState to manage item insertions and removals. The itemBuilder function is responsible for creating each list item, and the insertItem and removeItem methods provide built-in animations for these operations.
Key Features of AnimatedList
- Dynamic Updates: Efficiently animates list items when added or removed
- Custom Animations: Allows custom animations for item removals using
removeItem - Scrolling: Supports horizontal and vertical scrolling with custom physics
10. Using Rive Animations
Rive is a powerful animation platform that allows developers to integrate high-quality animations into Flutter apps with ease.
| Feature | Description | Notes |
|---|---|---|
| rive_flutter | rive_flutter: ^0.5.0 |
Official Flutter package for Rive animations |
| RiveAnimation | RiveAnimation.asset('assets/animation.riv') |
Load Rive animation from asset |
| RiveAnimation.play | RiveAnimation.play('animationName') |
Play specific animation by name |
Example: Loading a Rive Animation
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class RiveExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Rive Animation')),
body: Center(
child: RiveAnimation.asset('assets/animation.riv'),
),
);
}
}
Example: Controlling Animation Playback
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class PlayableRiveExample extends StatefulWidget {
@override
_PlayableRiveExampleState createState() => _PlayableRiveExampleState();
}
class _PlayableRiveExampleState extends State<PlayableRiveExample> {
bool isPlaying = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Controlled Rive')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RiveAnimation.asset('assets/animation.riv'),
ElevatedButton(
onPressed: () {
setState(() {
isPlaying = !isPlaying;
});
},
child: Text(isPlaying ? 'Pause' : 'Play'),
),
],
),
),
);
}
}
pubspec.yaml under flutter: and assets:.
rive_flutter package and must be properly configured in your project.