Mobile Dev
Flutter animations - four different ways to implement animations in your flutter app
August 31, 2023
Flutter animations - four different ways to implement animations in your flutter app
Introduction:
In the world of mobile app development, animations serve as the glue that binds functionality and aesthetics, breathing life into user interfaces and creating delightful user experiences. As the demand for rich and interactive apps continues to rise, developers are seeking tools and frameworks that facilitate the seamless integration of captivating animations into their projects.
Enter Flutter, a cross-platform UI toolkit developed by Google. Renowned for its ability to create stunning and performant user interfaces, Flutter offers a variety of animation techniques that empower developers to bring their apps to life. In this blog post, we'll embark on a journey through the realm of Flutter animations, exploring four distinct approaches to achieving captivating motion within your apps.
From the simplicity of third-party packages to the intricate control offered by Flutter's built-in animation framework, we'll delve into each method's strengths, weaknesses, and ideal use cases. By the end of this post, you'll have a comprehensive understanding of different animation techniques, enabling you to make informed decisions when animating your Flutter applications.
So, whether you're a newcomer to Flutter seeking to enhance your app's visual appeal or an experienced developer aiming to master advanced animation strategies, join us as we unravel the magic of animations in Flutter. Let's dive into the world of motion, creativity, and user engagement that Flutter animations offer.
See the code for this whole project on: https://github.com/NakuDoka/flutter_animations
1. Flutter's Built-in Animation Framework
Flutter provides a robust built-in animation framework that empowers developers to create smooth and interactive animations. This framework utilizes the concepts of AnimationControllers, Tweens, and AnimatedWidgets to orchestrate animations seamlessly. Let's explore this technique through the following examples.
A. Heart Animation with AnimationController and AnimatedBuilder
Imagine implementing a heart icon that scales and changes color when tapped. Here's how you can achieve this effect using AnimationController
and AnimatedBuilder
:
class _HeartState extends State<Heart> with SingleTickerProviderStateMixin {
bool isFav = false;
late AnimationController _controller;
late Animation<Color?> _colorAnimation;
late Animation<double> _sizeAnimation;
late Animation<double> _curve;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_curve = CurvedAnimation(parent: _controller, curve: Curves.slowMiddle);
_colorAnimation = ColorTween(begin: Colors.grey[400], end: Colors.red).animate(_curve);
_sizeAnimation = TweenSequence(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 30, end: 50),
weight: 50,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 50, end: 30),
weight: 50,
),
]).animate(_curve);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
isFav = true;
});
}
if (status == AnimationStatus.dismissed) {
setState(() {
isFav = false;
});
}
});
// dismiss the animation when widgit exits screen
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, _) {
return IconButton(
icon: Icon(
Icons.favorite,
color: _colorAnimation.value,
size: _sizeAnimation.value,
),
onPressed: () {
isFav ? _controller.reverse() : _controller.forward();
},
);
});
}
}
In this example, _controller
is an AnimationController
responsible for managing the animation's state. _colorAnimation
and _sizeAnimation
are animations that control the heart icon's color and size respectively. When the icon is tapped, the _controller
toggles between forward and reverse animations.
B. TweenAnimationBuilder for Screen Title Animation
Suppose you want to animate the appearance of a screen title with a fading and sliding effect. Here's how you can achieve this using TweenAnimationBuilder
:
import 'package:flutter/material.dart';
class ScreenTitle extends StatelessWidget {
// Constructor and build...
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: 1),
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
builder: (BuildContext context, double _val, Widget? child) {
return Opacity(
opacity: _val,
child: Padding(padding: EdgeInsets.only(top: _val * 20), child: child),
);
},
child: Text(
text,
style: const TextStyle(fontSize: 36, color: Colors.white, fontWeight: FontWeight.bold),
),
);
}
}
In this example, TweenAnimationBuilder
smoothly transitions the opacity and padding of the title text, creating an elegant fading and sliding animation.
C. AnimatedList for Trip List Animation
Animating lists can be captivating, and Flutter's AnimatedList
simplifies the process. Consider animating the appearance of a list of trips using SlideTransition
:
class _TripListState extends State<TripList> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<Widget> _tripTiles = [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_addTrips();
});
}
void _addTrips() {
// get data from db
List<Trip> trips = [
Trip(title: 'Beach Paradise', price: '350', nights: '3', img: 'beach.png'),
Trip(title: 'City Break', price: '400', nights: '5', img: 'city.png'),
Trip(title: 'Ski Adventure', price: '750', nights: '2', img: 'ski.png'),
Trip(title: 'Space Blast', price: '600', nights: '4', img: 'space.png'),
];
Future ft = Future(() {});
for (var trip in trips) {
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 100), () {
_tripTiles.add(_buildTile(trip));
_listKey.currentState?.insertItem(_tripTiles.length - 1);
});
});
}
}
Widget _buildTile(Trip trip) {
return ListTile(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => Details(trip: trip)));
},
contentPadding: const EdgeInsets.all(25),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('${trip.nights} nights',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.blue[300])),
Text(trip.title, style: TextStyle(fontSize: 20, color: Colors.grey[600])),
],
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Hero(
tag: 'location-img-${trip.img}',
child: Image.asset(
'images/${trip.img}',
height: 50.0,
),
),
),
trailing: Text('\\$${trip.price}'),
);
}
final Tween<Offset> _offset = Tween(begin: const Offset(1, 0), end: const Offset(0, 0));
@override
Widget build(BuildContext context) {
return AnimatedList(
key: _listKey,
initialItemCount: _tripTiles.length,
itemBuilder: (context, index, animation) {
return SlideTransition(
position: animation.drive(_offset),
child: _tripTiles[index],
);
});
}
}
In this example, _addTrips
method adds trips to the list with a slide-in animation using SlideTransition
and _offset
. The _offset
Tween controls the sliding animation's direction.
The Hero Widget for Seamless Transitions
In Flutter, the Hero
widget allows smooth transitions between two screens by animating shared elements. This is achieved by providing the same tag
to elements on both screens. Let's see how you're using it:
Page 1 (List of Trips):
Hero(
tag: 'location-img-${trip.img}',
child: Image.asset(
'images/${trip.img}',
height: 50.0,
),
),
Page 2 (Details Screen):
Hero(
tag: 'location-img-${trip.img}',
child: Image.asset(
'images/${trip.img}',
height: 360,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
By using the same tag
for the image on both screens, Flutter smoothly transitions the image between the list and details pages, providing a visually pleasing effect
Conclusion
Flutter's built-in animation framework offers a powerful arsenal of tools for creating visually appealing and interactive animations. By combining AnimationControllers, Tweens, and AnimatedBuilders, developers can effortlessly achieve captivating motion effects that enhance the user experience. This technique provides granular control over animations and enables developers to customize their app's look and feel down to the finest details. Link to package: https://pub.dev/packages/flutter_animate
In the next section, we'll explore another popular animation method: flutter_animate
2. Flutter_Animate Package
To explore another method of achieving animations in Flutter, I decided to recreate the same animations we discussed earlier using the flutter_animate
package. This package offers a straightforward way to animate widgets with various effects, making it an attractive choice for developers seeking simplicity in their animations.
A. Screen Title Animation
In the previous section, we used TweenAnimationBuilder
to create a smooth title animation. With flutter_animate
, the same animation can be achieved with a more concise syntax:
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
class ScreenTitle extends StatelessWidget {
final String text;
const ScreenTitle({super.key, required this.text});
@override
Widget build(BuildContext context) {
return Text(
text,
style: TextStyle(fontSize: 36, color: Colors.white, fontWeight: FontWeight.bold),
)
.animate()
.fadeIn(duration: Duration(milliseconds: 500), curve: Curves.easeIn)
.slideY(duration: Duration(milliseconds: 500), curve: Curves.easeIn);
}
}
Here, the flutter_animate
package streamlines the animation creation process, offering a convenient and readable way to apply both fade-in and vertical sliding effects to the title.
B. Trip List Animation
Similarly, the TripList
animation using flutter_animate
is achieved with a simpler structure:
dartCopy code
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
class TripList extends StatefulWidget {
// ... (constructor and other methods)
@override
Widget build(BuildContext context) {
return Column(
children: AnimateList(
delay: 100.ms,
interval: 100.ms,
effects: [SlideEffect(begin: Offset(1, 0), end: Offset(0, 0))],
children: _tripTiles),
);
}
}
The AnimateList
widget, part of flutter_animate
, allows us to apply a slide-in effect to each trip tile in the list seamlessly. The delay
, interval
, and effects
parameters make it easy to customize the animation's timing and characteristics.
C. Heart Animation
One interesting point to note is that I was unable to recreate the heart icon animation using flutter_animate
. This illustrates the importance of choosing the appropriate animation method for the task at hand. The rich functionalities provided by Flutter's built-in animation framework might be more suitable for complex interactions like the heart animation.
In Conclusion
Exploring the flutter_animate
package provides insights into a simplified approach to animations. While it proves effective for certain animations, more intricate interactions might still require Flutter's native animation capabilities. Knowing when to choose between different animation techniques is key to creating fluid and engaging user experiences.
In the next section, we'll delve into another exciting realm of animation: Rive for Flutter.
3. Rive for Flutter
Rive is an exciting animation and design tool that seamlessly integrates with Flutter, enabling developers to create complex animations and interactive designs for their apps. With Rive, animations can be built and edited visually, offering a rich set of features for creating captivating user experiences. Read more here: https://rive.app/
A. Using Rive for One-Shot Animations
To illustrate Rive's capabilities, I decided to implement a loading animation using the RiveAnimation widget. This example demonstrates a simple loading animation that plays once and then stops.
class RiveAnimationTest extends StatefulWidget {
/// Controller for playback
late RiveAnimationController _controller;
@override
void initState() {
super.initState();
_controller = OneShotAnimation(
'walk',
autoplay: true,
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ... (other widgets)
RiveAnimation.asset(
'rives/load.riv',
controllers: [_controller],
fit: BoxFit.fitHeight,
),
// ... (other widgets)
],
);
}
}
In this case, the RiveAnimation.asset
widget displays an animation from the 'rives/load.riv' file. The animation is controlled by the _controller
, which plays the animation once and stops.
B. Leveraging Rive's State Machines
Rive's power truly shines when utilizing state machines to manage complex animations. For instance, consider the scenario where users choose between two modes, 'Numbers' and 'Images', each accompanied by a different animation.
class RiveStateTest extends StatefulWidget {
SMINumber? _bump;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
bool isNumbers = false;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(artboard, 'loop');
artboard.addController(controller!);
_bump = controller.findInput<double>('state') as SMINumber;
}
void _hitBump(int value) => _bump?.value = value.toDouble();
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// ... (other widgets)
RiveAnimation.asset(
'rives/choose_mode.riv',
stateMachines: const ['loop'],
onInit: _onRiveInit,
fit: BoxFit.fitHeight,
),
// ... (other widgets)
],
);
}
}
In this case, the RiveAnimation.asset
widget is configured to work with state machines. The animation is driven by a state machine called 'loop', which enables dynamic transitions and interactions within the animation.
C. The Difference: One-Shot vs. State Machines
The key distinction between the two examples lies in their behavior. While the first example using OneShotAnimation
plays a single animation, the second example using Rive's state machines offers more intricate control. State machines allow dynamic transitions between different states of the animation, offering a more advanced and interactive experience.
In Conclusion
Rive adds a new dimension to Flutter animations by offering both simple and complex animation capabilities. Whether you're looking to create a quick loading animation or build complex state-based interactions, Rive seamlessly integrates with Flutter, providing developers with powerful tools to elevate their app's visual appeal and user engagement.
In the next section, we'll explore the world of third-party animation packages found on pub.dev.
4. Exploring Third-party Animation Packages
Flutter's vibrant ecosystem on pub.dev offers an array of third-party animation packages, each tailored to different animation needs. These packages provide developers with ready-made solutions for adding delightful animations to their apps with minimal effort.
Exploring these packages can often save development time and effort, while still achieving engaging and visually appealing results. Let's take a quick look at some examples:
animated_toggle_switch:
auto_animated:
animated_text_kit:
5. Comparative Analysis
When to Choose Which Technique:
- Flutter's Built-in Framework: Choose this for complex and interactive animations where fine-tuned control is required. This method offers robust performance and is well-supported by the Flutter community.
flutter_animate
Package: Opt for this when you want to quickly add simple animations with minimal code. While it's easier to use, it might be less flexible for complex animations.- Rive for Flutter: Use Rive for sophisticated vector-based animations and interactive designs. State machines in Rive provide advanced control over animations, making it ideal for complex scenarios.
6. Best Practices for Animation in Flutter
Here are some best practices to keep in mind when working with animations in Flutter:
- Subtlety and Smoothness: Focus on creating animations that enhance the user experience without overwhelming them. Smooth and gradual animations are often more pleasing to the eye.
- Animation States: Efficiently manage animation states to avoid unnecessary resource consumption. Utilize widgets like
AnimatedBuilder
to animate specific parts of your UI, optimizing performance. - Performance Optimization: Consider performance implications when choosing animation techniques. Use techniques like
AnimatedContainer
to leverage Flutter's hardware acceleration for smoother animations.
Conclusion
In this exploration of animation techniques in Flutter, we've covered a spectrum of options to breathe life into your app's UI. From Flutter's built-in framework to external packages like flutter_animate
and the powerful Rive tool, each technique offers a unique approach to animations.
As Flutter continues to evolve, developers have an ever-expanding toolkit to create captivating user experiences. Remember, the best technique depends on your project's needs and complexity. Embrace the dynamic Flutter community and resources to continue mastering the art of animations and taking your Flutter apps to new heights.
Whether you're aiming for elegance, interactivity, or a combination of both, Flutter's animation capabilities are at your disposal.
Happy animating!
Share