Essential Flutter Widgets for Your Apps
Flutter is a mobile app development framework created by Google that has gained popularity for its …
read moreAnimations are a key element in modern application development. They not only make an interface visually more appealing, but also enhance the user experience by providing smooth transitions and intuitive effects, as long as they are well-designed and do not interfere with the application’s usability.
Animations are a key element in modern application development. They not only make an interface visually more appealing, but also enhance the user experience by providing smooth transitions and intuitive effects, as long as they are well-designed and do not interfere with the application’s usability.
Throughout the Flutter series, we have explored different concepts, from the Dart language,creating user interfaces,the most common widgets, to asynchrony.This time, we will learn how to create animations to give our applications a more dynamic and engaging look.
Flutter, thanks to its highly optimized graphics engine, makes it easy to implement animations without affecting application performance. In this article, we will explore three basic types of animations in Flutter, with practical and easy-to-implement examples:
AnimationController
.Next, we will develop an application that will allow us to
showcase
each of these types of animations in a single app while analyzing the most common use cases and their possible
applications.
Flutter offers a powerful and flexible animation system that adapts to different needs.
To begin with, it is essential to understand the three most basic animations that will allow us to bring our UI to life
in a simple and effective way.
Below, we will explore how they work, how they differ, and when it is advisable to use them.
Implicit animations are the easiest to implement in Flutter. They are used when we want certain
state changes in a widget to be animated automatically without the need to manually control timing or interpolation.
AnimationController
is not required, as the animation is managed internally.AnimatedContainer
– Animates properties like size, color, and borders.AnimatedOpacity
– Changes the transparency of a widget.AnimatedAlign
– Smoothly modifies a widget’s alignment.AnimatedSwitcher
– Animates the transition between widgets when they are replaced.Explicit animations provide greater control over timing, speed, and animation behavior.
They are useful when we want to define more precise transitions or when animations depend on more complex interactions.
AnimationController
is used to manage the animation timing.Tween
, and animation curves are applied with CurvedAnimation
.AnimatedBuilder
can be used to optimize performance by rebuilding only the necessary parts of the widget.Hero animations allow smooth transitions between screens when an element maintains its visual appearance in both views.
They are ideal for improving the navigation experience in an app.
Hero
widget is used, assigning a shared tag (tag
) to a widget on both screens.We noticed a small issue in our practical example where the text size does not adjust correctly during the Hero animation.
It would be worth investigating the cause and fixing it, as it is likely related to how the widget’s size is being managed during the transition.
In the next part of the article, we will create a Flutter application that demonstrates each of these animations with practical examples.
This Flutter application allows us to explore the three types of animations mentioned earlier:
AnimationController
.Next, we will explain the purpose of each screen and the widgets used.
The main screen serves as a menu to access the different animations. It contains:
Card
) that act as buttons to navigate to each animation screen.Scaffold
: Base structure of the screen.Column
: Organizes elements in a vertical list.Card
: Creates visually appealing buttons.InkWell
: Detects taps on each card.Navigator
: Handles navigation to the animation screens.
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animation Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Explore Different Types of Animations',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 30),
_buildAnimationButton(
context,
'Implicit Animations',
'Automatically animate size, color, opacity, and alignment.',
Icons.animation,
ImplicitAnimationsScreen(),
),
SizedBox(height: 20),
_buildAnimationButton(
context,
'Explicit Animations',
'Manually control scaling animations with start/reverse buttons.',
Icons.touch_app,
ExplicitAnimationsScreen(),
),
SizedBox(height: 20),
_buildAnimationButton(
context,
'Hero Animation',
'Tap the widget to see a smooth transition between screens.',
Icons.flight_takeoff,
HeroAnimationScreen(),
),
],
),
),
);
}
Widget _buildAnimationButton(BuildContext context, String title, String description, IconData icon, Widget screen) {
return Card(
elevation: 4,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => screen),
);
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Icon(icon, size: 40, color: Colors.blue),
SizedBox(height: 10),
Text(
title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
SizedBox(height: 5),
Text(
description,
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
–
This screen demonstrates how to use implicit animations in Flutter.
State changes in widgets automatically trigger transitions in size, color, and alignment.
It includes three animation examples:
AnimatedContainer
– Changes size and color when a button is pressed.AnimatedOpacity
– Adjusts a widget’s transparency.AnimatedAlign
– Modifies a widget’s alignment within its container.Each animation is triggered by pressing a button, changing the screen’s state.
AnimatedContainer
: For smooth transitions in visual properties.AnimatedOpacity
: For fade-in and fade-out effects.AnimatedAlign
: To animate a widget’s positioning.FloatingActionButton
: To navigate back to the main screen.
class _ImplicitAnimationsScreenState extends State<ImplicitAnimationsScreen> {
bool _expanded = false;
bool _visible = true;
Alignment _alignment = Alignment.topLeft;
void _toggleExpanded() {
setState(() {
_expanded = !_expanded;
});
}
void _toggleVisibility() {
setState(() {
_visible = !_visible;
});
}
void _changeAlignment() {
setState(() {
_alignment = _alignment == Alignment.topLeft
? Alignment.bottomRight
: Alignment.topLeft;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Implicit Animations'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'1. AnimatedContainer',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the button to toggle size and color.'),
SizedBox(height: 10),
Center(
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _expanded ? Colors.blue : Colors.red,
child: Center(
child: Text(
_expanded ? 'Expanded' : 'Small',
style: TextStyle(color: Colors.white),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleExpanded,
child: Text('Toggle Container'),
),
SizedBox(height: 30),
Text(
'2. AnimatedOpacity',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the button to toggle visibility.'),
SizedBox(height: 10),
Center(
child: AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(seconds: 1),
child: Container(
width: 100,
height: 100,
color: Colors.green,
child: Center(
child: Text(
_visible ? 'Visible' : 'Invisible',
style: TextStyle(color: Colors.white),
),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleVisibility,
child: Text('Toggle Opacity'),
),
SizedBox(height: 30),
Text(
'3. AnimatedAlign',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the button to change alignment.'),
SizedBox(height: 10),
Container(
height: 200,
width: double.infinity,
color: Colors.grey[200],
child: AnimatedAlign(
alignment: _alignment,
duration: Duration(seconds: 1),
child: Container(
width: 100,
height: 100,
color: Colors.orange,
child: Center(
child: Text(
_alignment == Alignment.topLeft
? 'Top Left'
: 'Bottom Right',
style: TextStyle(color: Colors.white),
),
),
),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _changeAlignment,
child: Text('Change Alignment'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
);
}
}
This screen demonstrates the use of explicit animations, where the developer manually controls the animation using an AnimationController
.
It includes an example where a Container
smoothly changes size:
AnimationController
: Controls the animation’s duration and state.Tween<double>
: Defines a range of values for interpolation.CurvedAnimation
: Applies an acceleration and deceleration curve to the animation.AnimatedBuilder
: Optimizes performance by avoiding unnecessary rebuilds.FloatingActionButton
: A standard floating button, used here to return to the main screen.
class _ExplicitAnimationsScreenState extends State<ExplicitAnimationsScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
bool _isAnimating = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
_animation = Tween<double>(begin: 100, end: 200).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
}
void _startAnimation() {
_controller.forward();
setState(() {
_isAnimating = true;
});
}
void _reverseAnimation() {
_controller.reverse();
setState(() {
_isAnimating = false;
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Explicit Animations'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'1. Scaling Animation',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Use the buttons to start or reverse the animation.'),
SizedBox(height: 20),
Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.purple,
child: Center(
child: Text(
_isAnimating ? 'Expanding' : 'Shrinking',
style: TextStyle(color: Colors.white),
),
),
);
},
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _startAnimation,
child: Text('Start Animation'),
),
SizedBox(width: 20),
ElevatedButton(
onPressed: _reverseAnimation,
child: Text('Reverse Animation'),
),
],
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
);
}
}
To conclude, we implement a Hero animation for a smooth transition between screens.
Hero
: Defines the widget that will be animated between screens.Navigator
: Manages navigation between screens.GestureDetector
: Detects user taps to trigger the animation.
class HeroAnimationScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Hero Animation'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'1. Hero Transition',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text('Tap the widget to see a smooth transition to the next screen.'),
SizedBox(height: 20),
Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HeroDetailScreen()),
);
},
child: Hero(
tag: 'heroTag',
child: Container(
width: 100,
height: 100,
color: Colors.teal,
child: Center(
child: Text(
'Tap Me!',
style: TextStyle(color: Colors.white),
),
),
),
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
);
}
}
As we mentioned earlier, there is a small issue with the Hero animation where the text does not adjust correctly when resizing.
This could be a great starting point for further investigation and improving our understanding of this type of animation.
While we have highlighted the advantages of using animations in our applications, it is also important to consider that excessive animations
can lead to a confusing or unpleasant user experience. Balancing the amount and intensity of animations is key to ensuring a smooth and effective user experience.
We have covered the basic concepts of animations in Flutter and created an example application
that demonstrates each of these types of animations in action. Now, you can apply this knowledge to your own projects and bring your user interfaces to life
with engaging and effective animations. I hope you found this guide helpful, and most importantly… Happy Coding!
That may interest you
Flutter is a mobile app development framework created by Google that has gained popularity for its …
read moreIntroduction to asynchrony in programming In the world of programming, one of the biggest challenges …
read moreIntroduction In the previous chapters of our series on Flutter, we’ve laid down a solid …
read moreConcept to value