Explore some animation used in a flutter
When we work on any mobile application there may be chances that we have to use animations. Flutter provides various types of animation and there are multiple animated widgets that have their own advantages. In flutter, we have implicit animations and explicit animations.
Implicit animations mean the flutter provides animations and we have to just provide what type of animation we have to use. This type of animation is not highly customizable but by using implicit animation we can achieve animation fastly. (like Animated container, Animated positioned, etc)
Explicit animation gives us chance to customize animation according to our needs. We can use the controller of animation to do the animation as required.
In this article, we will see animation such as
Page transiton : Animation effects while navigating from one screen to another.
Hero animation : To see the widgets moving from one screen to another.
Animated container : Animate the properties of container.
Animated padding : Animate the padding of widget
Animated positioned : To change the position of the widget.
Animated opacity : Animate the opacity of widget.
Animated icons : To animate the icon.
Page transitions
Page transition animation is the animation that is shown when the user moves from one screen to another screen. To achieve page transition animation we will use the get package. The reason we are using this package is that we can achieve page transition easily with less code.
Got to pub.dev and search for get package. Check the latest version and add it to the pubspec.yaml file.
Let's create a common widget for the button which can be reusable through the app.
outlined_button_widget.dart
import 'package:flutter/material.dart';
class OutlinedButtonWidget extends StatelessWidget {
final String? buttonText;
final VoidCallback? onTap;
final double? width;
const OutlinedButtonWidget(
{super.key, @required this.buttonText, @required this.onTap, this.width});
@override
Widget build(BuildContext context) {
return OutlinedButton(
style: OutlinedButton.styleFrom(
fixedSize: Size(width ?? MediaQuery.of(context).size.width * 0.9,
MediaQuery.of(context).size.height * 0.05),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0))),
onPressed: onTap ?? () {},
child: Text(
buttonText ?? "",
style: const TextStyle(color: Colors.white),
));
}
}
Now in the main.dart file we have to change the MaterialApp to GetMaterialApp to use the features of the get package.
main.dart
import 'package:animation_demo/screens/list_of%20_animations.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Animations Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
appBarTheme: AppBarTheme(
backgroundColor: Colors.blue.shade300,
elevation: 0.0,
)),
home: const ListOfAnimations(),
);
}
}
Now create a page where we display the list of animations and name it list_of_animations.dart.
In this page, we will display the list of buttons that will navigate to another page to show the demo of animations.
list_of_animations.dart
import 'package:animation_demo/screens/animated_container/animated_container_example.dart';
import 'package:animation_demo/screens/animated_opacity/animated_opacity_example.dart';
import 'package:animation_demo/screens/animated_positioned/animated_positioned_example.dart';
import 'package:animation_demo/screens/hero_animation/hero_animation.dart';
import 'package:animation_demo/screens/page_transition/page_one.dart';
import 'package:animation_demo/widgets/outlined_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'animated_icon/animated_icon_example.dart';
import 'animated_padding/animated_padding_example.dart';
class ListOfAnimations extends StatefulWidget {
const ListOfAnimations({super.key});
@override
State<ListOfAnimations> createState() => _ListOfAnimationsState();
}
class _ListOfAnimationsState extends State<ListOfAnimations> {
List<String> animationList = [
"Page transition",
"Hero animation",
"Animated container",
"Animated padding",
"Animated positioned",
"Animated opacity",
"Animated Icon "
];
/// common function to navigate to other screen
goToPage(int index) {
switch (index) {
case 0:
Get.to(const PageOne());
break;
case 1:
Get.to(const HeroAnimation());
break;
case 2:
Get.to(const AnimatedContainerExample());
break;
case 3:
Get.to(const AnimatedPaddingExample());
break;
case 4:
Get.to(const AnimatedPositionedExample());
break;
case 5:
Get.to(const AnimatedOpacityExample());
break;
case 6:
Get.to(const AnimatedIconExample());
break;
default:
Get.to(const PageOne());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.all(20),
child: ListView.separated(
separatorBuilder: (context, index) => Container(),
itemCount: animationList.length,
itemBuilder: (context, index) => OutlinedButtonWidget(
buttonText: animationList[index],
onTap: () {
goToPage(index);
}),
),
));
}
}
Now let's create two pages and name them page_one and page_two. On page one we will add a button to navigate to page two. We will move to page two by using Get.to(PageName()) because it provides a transition property through which we can have transition effects while navigating.
page_one.dart
import 'package:animation_demo/screens/page_transition/page_two.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../widgets/outlined_button_widget.dart';
class PageOne extends StatefulWidget {
const PageOne({super.key});
@override
State<PageOne> createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue.shade300,
appBar: AppBar(
title: const Text("Page 1"),
),
body: Center(
child: OutlinedButtonWidget(
buttonText: "Go to Page 2",
onTap: () {
Get.to(const PageTwo(),
transition: Transition.circularReveal,/// to provide tramsition effect
duration: const Duration(seconds: 1));
}),
),
);
}
}
page_two.dart
import 'package:animation_demo/widgets/outlined_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PageTwo extends StatefulWidget {
const PageTwo({super.key});
@override
State<PageTwo> createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Page 2"),
),
body: Center(
child: OutlinedButtonWidget(
buttonText: "Go back to Page 1",
onTap: () {
Get.back();/// naviaget to previous screen
}),
),
);
}
}
Hero animation
Hero animation is animation which looks like widget is moving from one screen to another.
To add hero animationn wrap a widget with Hero widget and provide a tag which must be unique.
To understand it properly let's create a page that will display a list of images and on click of it image it will navigate to a new screen where the image is shown individually.
In this page we will display list of image which is coming from network url.
We will pass imageUrl, tag and description to new screen while navigating.
On click of image we can navigate to new screen where we can see the hero animation while navigating.
hero_animation.dart
import 'package:animation_demo/screens/hero_animation/details_page.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HeroAnimation extends StatefulWidget {
const HeroAnimation({super.key});
@override
State<HeroAnimation> createState() => _HeroAnimationState();
}
class _HeroAnimationState extends State<HeroAnimation> {
List<String> imageUrls = [];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue.shade300,
appBar: AppBar(
title: const Text("Hero animation"),
),
body: ListView.separated(
shrinkWrap: true,
separatorBuilder: (context, index) => const Divider(
color: Colors.white,
),
itemCount: 80,
itemBuilder: (context, index) {
return ListTile(
contentPadding: const EdgeInsets.all(10.0),
leading: InkWell(
onTap: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (_) => DetailPage(
tag: "demo ${index + 1}",/// unique tag to pass
imageUrl:
"https://picsum.photos/id/$index/200/300",
description: "Image no ${index + 1}",
)));
},
child: Hero(
tag: "demo ${index + 1}",/// unique tag
child: Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0)),
child: Image(
image: NetworkImage(
"https://picsum.photos/id/$index/200/300"),
fit: BoxFit.cover,
width: 140,
),
),
),
),
title: Text(
"Image no ${index + 1}",
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.white),
),
);
}),
);
}
}
Create another page where we will display individual images. Create a constructor for imageUrl, tag, and description which can be passed from hero_animation.dart screen. In this screen wrap an image with a hero widget to see the hero animation and use the tag property from the constructor for the hero tag widget.
detail+page.dart
import 'package:flutter/material.dart';
class DetailPage extends StatefulWidget {
final String? imageUrl;
final String? tag;
final String? description;
const DetailPage({super.key, this.imageUrl, this.tag, this.description});
@override
State<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Details page"),
),
backgroundColor: Colors.blue.shade300,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: BoxDecoration(
image: DecorationImage(image: NetworkImage(widget.imageUrl!)),
borderRadius: BorderRadius.circular(12.0)),
child: Hero(
tag: widget.tag!,
child: Image(
image: NetworkImage(widget.imageUrl!),
width: 300,
fit: BoxFit.cover,
),
),
),
const SizedBox(
height: 50,
),
Text(
widget.description!,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
color: Colors.white),
)
],
),
),
);
}
}
Animated Container
Animated container is a widget where we can animate the properties of container implicitly like height, width,color,etc.
An Animated container gives us a parameter curve used to animate the properties of the container. To achieve the animation we need to provide duration (in seconds/milliseconds) to the animated container so the animation will be visible till the duration. So the parameters curve and duration are compulsory.
Let's create a page and name it animated_container_example.dart.On this page, we will add AnimatedContainer Widget and Button to animate the height and width of the animated container. With a tap of the button, the height and width of the container will be changed.
animated_container_example.dart
import 'dart:math';
import 'package:animation_demo/widgets/outlined_button_widget.dart';
import 'package:flutter/material.dart';
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
double _width = 50;/// initial width of container
double _height = 50;/// initial height of container
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Animated container"),
),
body: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(seconds: 1),
curve: Curves.easeIn,/// type of animation curve
height: _height,
width: _width,
decoration: BoxDecoration(
color: Colors.red, borderRadius: BorderRadius.circular(8)),
),
const SizedBox(
height: 10.0,
),
OutlinedButtonWidget(
buttonText: "Click here",
onTap: () {
final random = Random();
_width = random.nextInt(300).toDouble();/// to generate random width
_height = random.nextInt(300).toDouble();/// to generate random height
setState(() {});/// to change the state
}),
const Text(
"Click on the button to change the height\nand width of the container",
textAlign: TextAlign.center,
)
],
),
),
);
}
}
Animated Padding
Animated padding is a widget used to animate the widget's padding implicitly.
To achieve the animation wrap the widget with AnimatedPadding and it is compulsary to pass the parameters value such as curve,duration and padding.
Let's create a page and name it animated_padding_example.dart. On this page, we will add a container and wrap it with AnimatedPadding and also add a button to change the padding of the container. With a tap of the button, we will change the value of padding.
import 'package:animation_demo/widgets/outlined_button_widget.dart';
import 'package:flutter/material.dart';
class AnimatedPaddingExample extends StatefulWidget {
const AnimatedPaddingExample({super.key});
@override
State<AnimatedPaddingExample> createState() => _AnimatedPaddingExampleState();
}
class _AnimatedPaddingExampleState extends State<AnimatedPaddingExample> {
double padding = 12.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Animated padding example"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedPadding(
padding: EdgeInsets.all(padding),
curve: Curves.easeInBack,
duration: const Duration(seconds: 1),
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.green.shade400.withOpacity(0.6),
borderRadius: BorderRadius.circular(20.0)),
),
),
OutlinedButtonWidget(
buttonText: "Click here",
onTap: () {
padding = padding == 12.0 ? 120.0 : 12.0;
setState(() {});
}),
const Text(
"Click on the button to change\nthe padding of container",
textAlign: TextAlign.center,
)
],
),
),
);
}
}
Animated positioned
Animated positioned is a widget that is used to animate its position implicitly. It is only used with the Stack widget. Animated positioned widgets has parameters such as top, down, left, right, etc where we can provide value to these parameters and animate the widget.
Let's create a page and name it animated_positioned_example.dart. On this page, we will add a container and move this container up, down, left, and right using the button.
animated_positioned_example.dart
import 'package:animation_demo/widgets/outlined_button_widget.dart';
import 'package:flutter/material.dart';
class AnimatedPositionedExample extends StatefulWidget {
const AnimatedPositionedExample({super.key});
@override
State<AnimatedPositionedExample> createState() =>
_AnimatedPositionedExampleState();
}
class _AnimatedPositionedExampleState extends State<AnimatedPositionedExample> {
bool isUp = false;
bool isDown = false;
bool isLeft = false;
bool isRight = false;
bool isCenter = true;
@override
void initState() {
// TODO: implement initState
super.initState();
isCenter = true;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Animated positioned example"),
),
body: Center(
child: Stack(
children: [
AnimatedPositioned(
top: isCenter
? 100
: isUp
? 40
: isDown
? 100
: 100,
left: isLeft
? 40
: isRight
? 160
: 100,
duration: const Duration(seconds: 1),
curve: Curves.easeInCubic,
child: Container(
height: 160,
width: 160,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20)),
)),
AnimatedPositioned(
duration: const Duration(seconds: 1),
top: 380,
left: 45,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
OutlinedButtonWidget(
width: MediaQuery.of(context).size.width * 0.3,
buttonText: "Move up",
onTap: () {
setState(() {
isUp = true;
isCenter = false;
isDown = false;
isLeft = false;
isRight = false;
});
}),
const SizedBox(
width: 60,
),
OutlinedButtonWidget(
buttonText: "Move down",
onTap: () {
setState(() {
isUp = false;
isCenter = false;
isDown = true;
isLeft = false;
isRight = false;
});
},
width: MediaQuery.of(context).size.width * 0.3,
),
],
),
),
AnimatedPositioned(
duration: const Duration(seconds: 1),
top: 440,
left: 45,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
OutlinedButtonWidget(
buttonText: "Move left",
onTap: () {
setState(() {
isUp = false;
isCenter = false;
isDown = false;
isLeft = true;
isRight = false;
});
},
width: MediaQuery.of(context).size.width * 0.3,
),
const SizedBox(
width: 60,
),
OutlinedButtonWidget(
buttonText: "Move right",
onTap: () {
setState(() {
isUp = false;
isCenter = false;
isDown = false;
isLeft = false;
isRight = true;
});
},
width: MediaQuery.of(context).size.width * 0.3,
),
],
),
)
],
),
),
);
}
}
On this page, we have a total of four buttons,
Move up : On click of this button, we will decrease the value of the top parameter so the widget will move up.
Move down : On click of this button, we will increase the value of the left parameter so the widget will move down.
Move left : On click of this button, we will decrease the value of the left parameter so the widget will move left.
Move right : On click of this button, we will increase the value of the left parameter so the widget will move right.
Animated opacity
Animated opacity is a widget that animates its opacity implicitly.
To animate the opacity of a widget wrap it with AnimatedOpacity and the opacity value should be given between the range of 0.0 to 1.0.
Let's create a page where we will hide and show the flutter logo using the AnimatedOpacity widget. On this page, we will add a flutter logo and wrap it with AnimatedOpacity and a button to hide and show the flutter logo. With the click of a button, we are changing the value of opacity due to which the flutter logo will disappear.
animated_opacity_example.dart
import 'dart:developer';
import 'package:animation_demo/widgets/outlined_button_widget.dart';
import 'package:flutter/material.dart';
class AnimatedOpacityExample extends StatefulWidget {
const AnimatedOpacityExample({super.key});
@override
State<AnimatedOpacityExample> createState() => _AnimatedOpacityExampleState();
}
class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> {
var _opacity = 1.0;/// initial value of opacity
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Animated opacity example"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedOpacity(
opacity: _opacity,
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,/// to show the animation
child: FlutterLogo(
size: MediaQuery.of(context).size.height * 0.25,
),
),
OutlinedButtonWidget(
buttonText: _opacity == 1.0 ? "Hide Logo" : "Show logo",
onTap: () {
_opacity = _opacity == 1.0 ? 0.0 : 1.0;/// changing the value of opacity to hide and show
setState(() {});/// to change the state
})
],
),
),
);
}
}
AnimatedIcon
AnimatedIcon is a widget that is used to animate the icon. It takes parameter progress which is the animation value in double and it is also a required field. Also, it takes icon property as an AnimatedIcon which is only supported for the animation.
Let's create a page as animate_icon_example.dart and extend it TickerProviderStateMixin. On this page, we will display an animated icon as a play and pause icon.
We have to extend it because we need to create the controller for the animation(AnimationController).
By using this controller we animate the widget in a reverse direction, or forward direction, stop the animation or set the value of the animation.
import 'package:flutter/material.dart';
class AnimatedIconExample extends StatefulWidget {
const AnimatedIconExample({super.key});
@override
State<AnimatedIconExample> createState() => _AnimatedIconExampleState();
}
class _AnimatedIconExampleState extends State<AnimatedIconExample>
with TickerProviderStateMixin {
bool isChanged = true;
late Animation<double> _myAnimation;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
_myAnimation = CurvedAnimation(curve: Curves.linear, parent: _controller);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Animated icon example"),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
onTap: () {
if (isChanged) {
_controller.forward();
} else {
_controller.reverse();
}
isChanged = !isChanged;
setState(() {});
},
child: AnimatedIcon(
size: 120.0,
icon: AnimatedIcons.play_pause,
color: isChanged ? Colors.green : Colors.red,
progress: _myAnimation),
),
Text(
isChanged ? "Play" : "Pause",
style:
const TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
],
),
),
);
}
}
In this article, we have explored some of the animated widgets. There are also other widgets that have their unique uses which can be explored one by one.
The full source code is available here.
Let me know in the comments if I need to correct anything. I will try to improve it.
Clap if you like the article. 👏