Animations In Flutter

Omesh
7 min readJan 13, 2019

--

Animations are one of the essential things that enhances the user experience, makes your application more interactive and makes your users excited to use the application.

This is My First Article in Medium and I thought of starting with a Cool animation example and Try to Cover the Basics of Animation in Flutter

If you are a Beginner and want to Understand the Basics of Flutter do Refer Tutorials on Youtube and Other Medium Posts. If you want My recommendation About Where to Start The Basics I would Recommend Understand the Basics of Dart Programming language.

Recommended Youtube Tuts (Flutter and Dart):

  1. SmartHerd
  2. MtechViral

For Advanced:

Try Fluttery, Tensor Programming These are just Amazing, Yet little Bit Tricky and Fun To Learn

OK, STOP

And Here is the Final Animation:

Elevation Animation

I call this an Elevation Animation…

why? You’ll Understand at the end of the tutorial

The full Code to This is Animation is available here

First: Intro To Animation Components

The First Component is Animation (obviously) and the Second is AnimationController. Animation object is the object that gets the values that are given and translates those to meaningful animations. AnimationController class is the class to control the animation objects. We are using it to start the animation objects, give them durations and many more.

Nextly,

Tween is a Component which is used to mapping from an input range to output range. i.e, it is used to change to values from an initial point to final point

Here’s the Basic Code:

import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/services.dart';

void main(){
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
runApp(Myapp());
}

class Myapp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Animation',
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('Animation')),
body: Container(),
);
}
}

The Above code Generates an app with UI theme as light(SystemUiOverlayStyle.light) and Creates a Scaffold Widget with an Appbar Titled as ‘Animation’ and a Container as a child.

You may wonder What is SingleTickerProviderStateMixin.

These Give access Ticker objects, The ticker objects listen to refresh rate and return numbers Which are Then provided to AnimationController for Further Animation

Ok, Now Create a Card with an elevation of value 30.0 inside a Container of a Center Widget

code:

Widget build(BuildContext context) {
return
Scaffold(
appBar: AppBar(title:Text('Animation')),
body: Center(
child:Container(
width: MediaQuery.of(context).size.width-100,
height: 300,
child:Card(
elevation: 30.0,
)
)
),
);
}

We gave the Container width as “MediaQuery.of(context).size.width-100"

The MediaQuery.of(context).size.width returns a value Dynamic Width Based on Your Device and (-100) makes it looks Good we subtract it by 100.

Your Output Should be like this if you Run The app:

Ok, Now let's Create Animation and AnimationController Components in the Main HomePageState

Animation<double> animation;
AnimationController animationController;

In Order to Initialize the Animation, We need to override the initState() method and we need to initialize the AnimationController and Animation and provide Animation Values To it.

...
AnimationController animationController;
void initState() {
// TODO: implement initState
super.initState();
animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 1000));
animation = Tween(begin: 30.0, end: 0.0).animate(animationController)
..addListener(() {
setState(() {});
});
animationController.forward();
}
@override
Widget build(BuildContext context) {
...

We providedvsync which prevents offscreen animations from consuming unnecessary resources and setting the duration as 1000millisecs(1 sec)

and we set animation values as Tween Component which begins with values 30.0 and ends with value “0.0” and {.animate(animationController)} and setState is to do change the animation State.

animation.forward() Begins the animation.

To Repeat the Animation Over and Over Again we Need to Listen For animation to Complete and reverse it and again. For that, we need a StatusListener and here it Goes:

...
setState(() {});
});
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
animationController.forward();
}
});
animationController.forward();
...

Now your Output Should Look Like this:

Gooooooood isn’t it!

Now Our {Future} Work is Going to be more Simpler, You may guess it by now!

We just need to add a card inside this card and Stack upon them Like this:

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Animation')),
body: Center(
child: Container(
width: MediaQuery.of(context).size.width - 100,
height: 300,
child: Card(
elevation: animation.value,
child: Padding(
padding: EdgeInsets.all(30.0),
child: Card(
elevation: animation.value,
child: Padding(
padding: EdgeInsets.all(30.0),
child: Card(
elevation: animation.value,
child: Padding(
padding: EdgeInsets.all(30.0),
child: Card(
elevation: animation.value,
)),
),
),
),
)))),
);
}

We Actually set the Values of Elevation to animation.value and We set the Padding in Order to Look Good.

And Looks like this:

And In Order to Start the Animation only when we Tap on the Center Card we need to add a Button as a child to the topmost card and set it's to onPressed value to start the animation when pressed. and Remove the Animation.forward() on the initState() method.

...
child: Card(
child: MaterialButton(onPressed: () {
animationController.forward();
}),
elevation: animation.value,
)),
...

Finally,

we need to do one more thing, which is to get that Beautiful animation in order to get that we need Couple of more aminationControllers and Animation value in which the AnimationControllers should begin the animation whenever the animation of the before Card finishes its Animation and reverse it as per its Card behind it.

Just Add Couple of more Animation, AnimationControllers with different names.

class _HomePageState extends State<HomePage> with TickerProviderStateMixin { Animation<double> animation; 
Animation<double> animation2;
Animation<double> animation3;
Animation<double> animation4;
AnimationController animationController;
AnimationController animationController2;
AnimationController animationController3;
AnimationController animationController4;
@override
void initState() {
...

and the logic is Pretty Simple and will be Explained below:

@override
void initState() {
super.initState();

animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));

animationController2 =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));

animationController3 =
AnimationController(vsync: this, duration: Duration(milliseconds: 600));

animationController4 =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));

animation = Tween(begin: 30.0, end: 0.0).animate(animationController)
..addListener(() {
setState(() {});
});

animation2 = Tween(begin: 30.0, end: 0.0).animate(animationController2)
..addListener(() {
setState(() {});
});

animation3 = Tween(begin: 30.0, end: 0.0).animate(animationController3)
..addListener(() {
setState(() {});
});

animation4 = Tween(begin: 30.0, end: 0.0).animate(animationController4)
..addListener(() {
setState(() {});
});

animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController2.forward();
}
});

animation2.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController3.forward();
} else if (status == AnimationStatus.dismissed) {
animationController.reverse();
}
});

animation3.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController4.forward();
} else if (status == AnimationStatus.dismissed) {
animationController2.reverse();
}
});

animation4.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController4.reverse();
} else if (status == AnimationStatus.dismissed) {
animationController3.reverse();
}
});
}

Basically, what we have done here is “addStatusListener” to all the Animation Components and if the Last Card Finishes its Animation we want the Respective AnimationController of the previous to Start its Animation!

and Vice Versa!!!

Don’t Forget to Change the Elevation Properties in Each Card to its Respective Animation Components.

And Change the SingleTickerProviderStateMixin to TickerProviderStateMixin In Order to Perform Multiple Animation as mentioned Earlier.

Here’s the Final Code:

import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/services.dart';

void main() {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);

runApp(Myapp());
}

class Myapp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Animation',
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}

class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
with TickerProviderStateMixin {
Animation<double> animation;
Animation<double> animation2;
Animation<double> animation3;
Animation<double> animation4;
AnimationController animationController;
AnimationController animationController2;
AnimationController animationController3;
AnimationController animationController4;

@override
void initState() {
super.initState();

animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));

animationController2 =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));

animationController3 =
AnimationController(vsync: this, duration: Duration(milliseconds: 600));

animationController4 =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));

animation = Tween(begin: 30.0, end: 0.0).animate(animationController)
..addListener(() {
setState(() {});
});

animation2 = Tween(begin: 30.0, end: 0.0).animate(animationController2)
..addListener(() {
setState(() {});
});

animation3 = Tween(begin: 30.0, end: 0.0).animate(animationController3)
..addListener(() {
setState(() {});
});

animation4 = Tween(begin: 30.0, end: 0.0).animate(animationController4)
..addListener(() {
setState(() {});
});

animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController2.forward();
}
});

animation2.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController3.forward();
} else if (status == AnimationStatus.dismissed) {
animationController.reverse();
}
});

animation3.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController4.forward();
} else if (status == AnimationStatus.dismissed) {
animationController2.reverse();
}
});

animation4.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController4.reverse();
} else if (status == AnimationStatus.dismissed) {
animationController3.reverse();
}
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Animation')),
body: Center(
child: Container(
width: MediaQuery.of(context).size.width - 100,
height: 300,
child: Card(
elevation: animation4.value,
child: Padding(
padding: EdgeInsets.all(30.0),
child: Card(
elevation: animation3.value,
child: Padding(
padding: EdgeInsets.all(30.0),
child: Card(
elevation: animation2.value,
child: Padding(
padding: EdgeInsets.all(30.0),
child: Card(
child: MaterialButton(onPressed: () {
animationController.forward();
}),
elevation: animation.value,
)),
),
),
),
)))),
);
}
}

The Duration of Each Animation Can be Increased and Decreased by Changing the Duration of the AnimationController.

To set the App in Portrait Mode ONLY add these to your main() function.

void main(){
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);

runApp(Myapp());
}

And Done!

Glad! You Gave me a couple of Claps!😊😊

Here’s my Github Repo! Hope you give Stars!🌟🌟

--

--