Posted in

How to Add Navigation Drawer Below App Bar and Set Menu Icon

How-to-Add-Navigation-Drawer-Below-App-Bar-and-Set-Menu-Icon-scaled
How-to-Add-Navigation-Drawer-Below-App-Bar-and-Set-Menu-Icon-scaled.png

How to Add a Navigation Drawer Below the App Bar and Set a Custom Menu Icon in Flutter

A well-structured navigation system is the backbone of a great user experience in any mobile application. In Flutter, the Drawer widget provides a convenient and familiar way for users to access different screens and features. While the default slide-in drawer is effective, you might have design requirements for a navigation menu that’s always visible or appears just below the app bar, especially on larger screens.

This comprehensive guide will walk you through everything you need to know about implementing navigation drawers in Flutter. We’ll start with the basics of adding a standard drawer and customizing its menu icon. Then, we’ll dive into the more advanced technique of creating a persistent navigation menu that sits below the app bar, a perfect solution for tablet and web layouts.

The Standard Flutter Navigation Drawer

Let’s begin with the conventional approach. The Scaffold widget in Flutter has a drawer property that makes it incredibly simple to add a slide-out navigation panel.

Here’s a basic implementation:

Dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FlutterStuff.com'),
        ),
        drawer: Drawer(
          child: ListView(
            padding: EdgeInsets.zero,
            children: <Widget>[
              const DrawerHeader(
                decoration: BoxDecoration(
                  color: Colors.blue,
                ),
                child: Text(
                  'Menu Header',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 24,
                  ),
                ),
              ),
              ListTile(
                leading: const Icon(Icons.home),
                title: const Text('Home'),
                onTap: () {
                  // Navigate to home screen
                },
              ),
              ListTile(
                leading: const Icon(Icons.settings),
                title: const Text('Settings'),
                onTap: () {
                  // Navigate to settings screen
                },
              ),
            ],
          ),
        ),
        body: const Center(
          child: Text('Your main content goes here!'),
        ),
      ),
    );
  }
}
Dart

In this code, we’ve added a Drawer to our Scaffold. The DrawerHeader provides a customizable space at the top, and the ListTile widgets act as our menu items. By default, Flutter automatically adds a “hamburger” icon (three horizontal lines) to the AppBar to open the drawer.

Customizing the Menu Icon

While the hamburger icon is universally recognized, you might want to use a custom icon to better match your app’s branding. You can achieve this by using the leading property of the AppBar.

Here’s how to set a custom menu icon:

Dart

appBar: AppBar(
  title: const Text('FlutterStuff.com'),
  leading: Builder(
    builder: (BuildContext context) {
      return IconButton(
        icon: const Icon(Icons.menu_open_rounded), // Your custom icon
        onPressed: () {
          Scaffold.of(context).openDrawer();
        },
        tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
      );
    },
  ),
),
Dart

We wrap the IconButton in a Builder widget to get a BuildContext that is a descendant of the Scaffold. This allows us to call Scaffold.of(context).openDrawer() to open the drawer when the custom icon is tapped.

Creating a Persistent Navigation Menu Below the App Bar

For applications that run on larger screens like tablets or desktops, a persistent navigation menu that sits to the side of the main content can be more user-friendly than a slide-out drawer. This is often referred to as a “side menu” or a “navigation rail.”

To achieve this “below the app bar” effect, we’ll structure our UI using a Row widget. This Row will contain our navigation menu and the main content area.

Here’s a conceptual layout:

Dart

Scaffold(
  appBar: AppBar(
    title: const Text('FlutterStuff.com'),
  ),
  body: Row(
    children: [
      // Your persistent navigation menu widget
      const MyNavigationMenu(),
      // A vertical divider for visual separation
      const VerticalDivider(thickness: 1, width: 1),
      // The main content area that will change based on selection
      Expanded(
        child: Container(
          // Your page content
        ),
      ),
    ],
  ),
);
Dart

Building a Responsive Navigation Menu

A key consideration for this pattern is responsiveness. On smaller screens, a persistent side menu can cramp the UI. The ideal solution is to show the persistent menu on larger screens and revert to the standard Drawer on smaller screens.

We can use a LayoutBuilder to determine the available screen width and adjust our layout accordingly.

Here is a more complete, responsive example:

Dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'FlutterStuff Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const ResponsiveNavigation(),
    );
  }
}

class ResponsiveNavigation extends StatefulWidget {
  const ResponsiveNavigation({super.key});

  @override
  State<ResponsiveNavigation> createState() => _ResponsiveNavigationState();
}

class _ResponsiveNavigationState extends State<ResponsiveNavigation> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FlutterStuff.com'),
      ),
      drawer: LayoutBuilder(
        builder: (context, constraints) {
          if (constraints.maxWidth < 600) {
            return const AppDrawer();
          } else {
            return null;
          }
        },
      ),
      body: LayoutBuilder(
        builder: (context, constraints) {
          if (constraints.maxWidth < 600) {
            return _buildMainContent();
          } else {
            return Row(
              children: [
                NavigationRail(
                  selectedIndex: _selectedIndex,
                  onDestinationSelected: (int index) {
                    setState(() {
                      _selectedIndex = index;
                    });
                  },
                  labelType: NavigationRailLabelType.all,
                  destinations: const [
                    NavigationRailDestination(
                      icon: Icon(Icons.home_outlined),
                      selectedIcon: Icon(Icons.home),
                      label: Text('Home'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.explore_outlined),
                      selectedIcon: Icon(Icons.explore),
                      label: Text('Explore'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.person_outline),
                      selectedIcon: Icon(Icons.person),
                      label: Text('Profile'),
                    ),
                  ],
                ),
                const VerticalDivider(thickness: 1, width: 1),
                Expanded(
                  child: _buildMainContent(),
                ),
              ],
            );
          }
        },
      ),
    );
  }

  Widget _buildMainContent() {
    switch (_selectedIndex) {
      case 0:
        return const Center(child: Text('Home Content'));
      case 1:
        return const Center(child: Text('Explore Content'));
      case 2:
        return const Center(child: Text('Profile Content'));
      default:
        return const Center(child: Text('Home Content'));
    }
  }
}

class AppDrawer extends StatelessWidget {
  const AppDrawer({super.key});

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: <Widget>[
          const DrawerHeader(
            decoration: BoxDecoration(
              color: Colors.blue,
            ),
            child: Text(
              'Menu',
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
              ),
            ),
          ),
          ListTile(
            leading: const Icon(Icons.home),
            title: const Text('Home'),
            onTap: () {
              // Handle navigation
              Navigator.pop(context); // Close the drawer
            },
          ),
          ListTile(
            leading: const Icon(Icons.explore),
            title: const Text('Explore'),
            onTap: () {
              // Handle navigation
              Navigator.pop(context);
            },
          ),
          ListTile(
            leading: const Icon(Icons.person),
            title: const Text('Profile'),
            onTap: () {
              // Handle navigation
              Navigator.pop(context);
            },
          ),
        ],
      ),
    );
  }
}
Dart

In this example:

  • We use a LayoutBuilder to check the maxWidth. If it’s less than 600 pixels (a common breakpoint for mobile), we display the standard Drawer.
  • On wider screens, we use a Row to display a NavigationRail alongside the main content. The NavigationRail is a Material Design widget perfect for this purpose.
  • We manage the selected state with _selectedIndex to highlight the active destination in the NavigationRail and to show the corresponding content.

Best Practices for Navigation Drawers

  • Keep it Concise: Avoid cluttering your navigation drawer with too many items. Prioritize the most important destinations in your app.
  • Provide Visual Feedback: Clearly indicate the currently active screen in the drawer, as shown in the NavigationRail example with selectedIndex.
  • Consistent Design: Ensure the design of your drawer aligns with the overall theme and branding of your application.
  • Think Responsively: As demonstrated, adapt your navigation pattern based on the screen size for an optimal user experience across all devices. For more in-depth tutorials and Flutter resources, be sure to check out other articles on FlutterStuff.com.

By understanding both the standard Drawer and how to create a custom, persistent navigation menu, you have the flexibility to build intuitive and user-friendly navigation experiences for any Flutter application.

Flutter Stuff Is A Team Of Passionate Flutter Developers On A Mission To Empower The Community. We Share Our Expertise And Insights Through Comprehensive Guides, Tutorials, And Resources, Making Flutter Mobile App Development Accessible And Enjoyable For Everyone.

Leave a Reply