Flutter Animated Navigation Drawer

A guide to develop Animated Navigation Drawer with Flutter

Muhammad Muzammil
The Startup

--

So here’s what we will be creating in this article:

Here’s the package I uploaded on pub.dev for this drawer. If you want to use that, you can use that in your application. I’ve added bit more options in that package for customization than this article, my main goal is to make this article easy and interesting and not to make it too complex. You can use this package if you want to save time or you can follow this article for learning purpose.

Okay I know the fact! I’ll be keeping it short as much as I can.

Lets begin the war!

Layout Holder

In Flutter we can overlap different widgets with Stack widget .

@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
FirstLayer(),
SecondLayer(),
ThirdLayer(),
HomePage(),
],
),
);
}

Don’t worry we’ll be creating all these widgets step by step in this article.

The FirstLayer widget will be the base widget which will hold the background color of the drawer menu.

@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF4c41a3),
Color(0xFF1f186f)
])),
);
}

This is a simple widget which hold downs a simple Container widget and colors of it are defined in LinearGradient.

We’ll skip the SecondLayer for now. We’ll focus on ThirdLayer.

@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(top: 100.0),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlutterLogo(
size: MediaQuery.of(context).size.width / 4,),
Row(
children: [
Text(
"FLUTTER",
style: TextStyle(
fontSize: 17,
color: Colors.white,
fontWeight: FontWeight.bold),),
Text(
"HOLIC",
style: TextStyle(
fontSize: 17,
color: Colors.blue[200],
fontWeight: FontWeight.bold),)
],)],),)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Home Screen",
style: TextStyle(
color: Colors.white,),),
Padding(
padding: EdgeInsets.only(bottom: 20),),
Text(
"Screen 2",
style: TextStyle(
color: Colors.white,),),
Padding(
padding: EdgeInsets.only(bottom: 20),),
Divider(
color: Color(0xFF5950a0),thickness: 2,),
Padding(
padding: EdgeInsets.only(bottom: 20),),
Text(
"About",
style: TextStyle(
color: Colors.white,),),
Padding(
padding: EdgeInsets.only(bottom: 20),),
Text(
"Share App",
style: TextStyle(
color: Colors.white,),),
Padding(
padding: EdgeInsets.only(bottom: 20),),
Divider(
color: Color(0xFF5950a0),thickness: 2,),
Padding(
padding: EdgeInsets.only(bottom: 20),),
Text(
"Bye",
style: TextStyle(
color: Colors.white,),),
Padding(
padding: EdgeInsets.only(bottom: 10),),
],),
],),
),);
}

This Layer will be containing all the content widgets of the drawer menu. You can understand the code, it’s pretty easy and straight forward I think.
I know many of you can arrange this arrangements much more affectively. My mission is to make it easy and understandable.

You can navigate in different parts (screens) of the app from this menu. I have included only one screen to make it as simple as possible.

For Animations

We will be using AnimatedContaier widget for making the basic animations of the widgets.
Lets start the interesting part of this article.

For the Last Layer or HomePage.
You need a package matrix4_transform, which will help us to transform the axis values, angles, and scale factor of AnimatedContainer widget.

class HomePage extends StatefulWidget {@override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {@override
Widget build(BuildContext context) {
return AnimatedContainer(
transform: Matrix4Transform()
.translate(x: 0, y: 0)
.rotate(0)
.matrix4,
duration: Duration(milliseconds: 250),
child: Container()
);
}
}

Now to open and close AnimatedContainer we will add IconButton in child Container with bool value to check the Drawer is open or not.

bool isOpen = false;@override
Widget build(BuildContext context) {

then design the child Container

child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.blue[200],),
child: SafeArea(
child: Stack(
children: [
!isOpen? IconButton(
icon: Icon(Icons.menu, color: Color(0xFF1f186f),),
onPressed: () {
setState((){
isOpen = true;
});
}): IconButton(icon: Icon(Icons.arrow_back_ios, color: Color(0xFF1f186f)),
onPressed: (){
setState((){
isOpen = true;
});
}),
Center(
child: Image.asset(
"assets/avatar.png",
height: MediaQuery.of(context).size.height / 2,),),],),)),

For AnimatedContainer to animate on open and close with tap on IconButton. We need to declare some variables and change values of them when the IconButton is pressed accordingly.

double xoffSet = 0;
double yoffSet = 0;
double angle = 0;
bool isOpen = false;@override
Widget build(BuildContext context) {

Change values of variables on IconButton press.

!isOpen? IconButton(
icon: Icon(Icons.menu, color: Color(0xFF1f186f),),
onPressed:(){
setState((){
xoffSet = 150;
yoffSet = 80;
angle = -0.2;

isOpen = true;
});
}): IconButton(icon: Icon(Icons.arrow_back_ios, color: Color(0xFF1f186f)),
onPressed:(){
setState((){
xoffSet = 0;
yoffSet = 0;
angle = 0;
isOpen = true;
});
}),

And lastly AnimatedContainer to transform with these values we need to pass these variables.

return AnimatedContainer(
transform: Matrix4Transform()
.translate(x: xoffSet, y: yoffSet)
.rotate(angle)
.matrix4,

Wollah! now the HomePage will be animating with the press on IconButton. That’s how the magic is done.

Lastly for Rounded Borders when the drawer is open, you need only thing to add in child Container of AnimatedContainer.

decoration: BoxDecoration(
color: Colors.blue[200],
borderRadius: isOpen?
BorderRadius.circular(10): BorderRadius.circular(0)
),

For the second last layer named as SecondLayer, which brings so much additional charm in the design. If anyone of you want to skip this container you can skip this part and continue after it.

The structure will be same as the HomePage or the last layer we built for it. I’ll simply paste the whole code.

class SecondLayer extends StatefulWidget {@override
SecondLayerState createState() => SecondLayerState();
}
class SecondLayerState extends State<SecondLayer> {double xoffSet = 0;
double yoffSet = 0;
double angle = 0;
bool isOpen = false;@override
Widget build(BuildContext context) {
return AnimatedContainer(
transform: Matrix4Transform()
.translate(x: xoffSet, y: yoffSet)
.rotate(angle)
.matrix4,
duration: Duration(milliseconds: 550),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: Color(0xFF4c41a3)),
child: Column(
children: [
Row(children: [],)],)
);
}
}

Now to animate this Widget with the HomePage you need to do few additional things.

Firstly you need to create an instance of the State of the SecondLayer so it can be updated from different places.

SecondLayerState secondLayerState;
class SecondLayerState extends State<SecondLayer> {
double xoffSet = 0;

And then you can put the state in it by at the time of creation of this widget by.

@override
Widget build(BuildContext context) {
secondLayerState = this;

Then in HomePage you need to add few things to update the state of SecondLayer with HomePage open and close.

!isOpen? IconButton(
icon: Icon(Icons.menu, color: Color(0xFF1f186f),),
onPressed:(){
setState((){
xoffSet = 150;
yoffSet = 80;
angle = -0.2;
isOpen = true;
});
secondLayerState.setState(() {
secondLayerState.xoffSet = 122;
secondLayerState.yoffSet = 110;
secondLayerState.angle = -0.275;
});

}):
IconButton(icon: Icon(Icons.arrow_back_ios, color: Color(0xFF1f186f)),
onPressed:(){
setState((){
xoffSet = 0;
yoffSet = 0;
angle = 0;
isOpen = true;
});
secondLayerState.setState(() {
secondLayerState.xoffSet = 0;
secondLayerState.yoffSet = 0;
secondLayerState.angle = 0;
});
}),

I also know the fact that state of this SecondLayer can be managed more effectively and efficiently with different State Management Patterns. But my prime goal is to make this article as short as and as easy as possible.

And with all this effort your end result will be like this. I hope.

So that’s how this cool looking Animated Drawer is made.
I hope you had enjoyed this in depth article. Here is the Github link for this article. The package uploaded on pub.dev is not based on this repo, it has its own repo.

As this is my first article. Please if this article helps you and if you like this article do appreciate and do like the package uploaded on pub.dev. If you find some mistake in it do correct me. Thank You.

--

--

Muhammad Muzammil
The Startup

Flutter Developer | Android Developer | Software Engineer