A Modern Navbar in Flutter

Karthik Nooli
Google for Developers Europe
5 min readFeb 14, 2021

So you're new to Flutter and you want to start exploring cool content and stuff to do with Flutter… Well, I hope this post helps!

The hell is Flutter? Well I’m not going to explain it in depth here, but it’s a brush, and Dart (the programming language) is the paint, then you have a canvas which would be the screen you want to paint on! That’s that.

Let’s talk about Navbars!

Navbars in apps are pretty boring, they’ve stayed the same for a while and I thought it was time to look for inspiration for a modern, new, and smooooth looking navbar! It was time I looked to Dribbble.com for some new content.

I found the one! The cleanest looking navbar I’ve ever seen!

Enough chatting, let’s code!

Let’s get to work! We start with the main structure for the widget. Let’s begin with the scaffold, and pop a new widget inside the `bottomNavigationBar`

@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: Navbar(),
);
}

Now let’s create the Navbar widget. Let’s start by figuring out what we need from the main app for the Navbar to work.

  • Background colour (bg)
  • Foreground/text colour (ec)
  • `List<String>` tags (the label bit under the icon) (elemTags)
  • `List<Icon>` icons (the icon bit above the label) (icons)
  • int current position to have ‘opened’ (current)
  • Function(int) to update the ‘current’ position on tap (onPressed)

This is what the build function looks like, it’s a container with a row with elements (spaced evenly) coming from the function _getChildren().

@override
Widget build(BuildContext context) {
return Container(
color: bg,
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _getChildren(),
));
}

So what does the _getChildren() function look like?

List<Widget> _getChildren() {
List<Widget> ret = [];
for (int i = 0; i < elemTags.length; i++) {
ret.add(Expanded(
child: NavbarElement(
elemTags[i], icons[i], onPressed, i, widget.current == i, this.ec),
));
}
return ret;
}

We’ve got a NavbarElement that is provided with the tag, icon, function, it’s position, and whether or not it’s ‘open’ or not. This is so that we can keep track of the open/close position for each element. Okie Dokie, now for the slightly more complicated section! How do we animate the NavbarElement and the icons and stuff? Well, let’s talk AnimatedPositioned (and AnimatedOpacity).

I’m not going to go through how implicit animations work as there are a million and one other tutorials out there! Here is the one by Google itself:

Wait, but how does that help us? Well, I plan to kinda have each element displaying the “icon” and when it’s tapped, it “scrolls” up and fades out, and the text fades in and scrolls into position! This all sounds a bit complicated, right? Thank god we are doing this in Flutter, any other language, I’d imagine it would be quite complicated. Let’s take this one bit at a time!

Let’s start with the parameters and structure…

class NavbarElement extends StatefulWidget {
String tag;
Icon icon;
Color color; //text colour
int position;
bool opened;
Function(int) press;

NavbarElement(
this.tag, this.icon, this.press, this.position, this.opened, this.color);

@override
_NavbarElementState createState() => _NavbarElementState();
}

class _NavbarElementState extends State<NavbarElement> {

@override
Widget build(BuildContext context) {
return Container();
}
}

is what I’ve got so far, this has the tag, icon, colour, position (in the array), and whether it should be displaying as opened or not! Okay, so let’s pop the two parts of the widget in a stack!

here is the code for the text and dot (a small container):

AnimatedOpacity(
curve: Curves.easeInOut,
opacity: widget.opened ? 1.0 : 0.0,
duration: Duration(milliseconds: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.tag,
style: TextStyle(
color: widget.color,
fontSize: 15.0,
fontWeight: FontWeight.bold),
),
Container(
margin: EdgeInsets.all(5.0),
height: 5,
width: 5,
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(2.5),
),
)
],
),
),

and then the icon:

AnimatedOpacity(
curve: Curves.easeInOut,
opacity: widget.opened ? 0.0 : 1.0,
duration: Duration(milliseconds: 300),
child: widget.icon),

And we put that inside the stack, making sure that the Icon is on top (in the code) of the text and dot.

So this doesn’t look right… yet! We’ve done most of the heavy lifting, now for the cool animation using AnimatedPositioned. For the two above widgets in the stack, we need to center them if they are meant to be displayed, otherwise, they are offset by an arbitrary amount. Let’s wrap them in an AnimatedPositioned, the following code is for the text and dot.

AnimatedPositioned(
right: 0,
left: 0,
bottom: widget.opened ? 0 : -50,
top: 0,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: AnimatedOpacity(...),
),

This says, center the widget if it’s meant to be displayed, otherwise, offset it by -50 (pulls it by -25 pixels) and we do the exact same (well, opposite)for the icon:

AnimatedPositioned(
right: 0,
left: 0,
top: widget.opened ? -50 : 0,
bottom: 0,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: AnimatedOpacity(...),
),

Now, all we have to do is update the “opened” parameter on tap, let’s add in the last bit of code, a gesture detector!

GestureDetector(
onTap: () {
widget.press(widget.position);
},
child: Container(...),
);

Brill! All you have to do is handle the state of the “opened” tab where you are calling `Navbar(…)`. This is how I did it:

Navbar(
Colors.blue,
Colors.white,
["Home", "Search", "Messages", "Account"],
[
Icon(
Icons.home,
color: Colors.white,
),
Icon(
Icons.search,
color: Colors.white,
),
Icon(
Icons.message,
color: Colors.white,
),
Icon(
Icons.person,
color: Colors.white,
),
],
opened, (i) {
setState(() {
opened = i;
});
}),

and

int opened = 0;

in the HomePage widget!

To Conclude

We’ve managed to make an awesome looking clean and minimalist navbar with a few simple lines of code, and that’s the incredible power of Flutter! It’s absolutely incredible to see the amazing improvements that the Flutter team is making every single day! So kudos to them :)

Here is a quick video of the finished product! Hope you enjoyed it :D

and finally the github repo:

--

--