How to customize bottom navigation bar

When we use Bottom Navigation Bar within a Scaffold in Flutter, we usually try to navigate to other pages from the bottom of our Flutter Application.

Quite naturally, we want to select a small number of views or widgets, typically between three to four and not more than five.

As, the name suggests the BottomNavigationBar is a widget that helps us to display multiple views so that we can navigate to those widgets. As a result, we can navigate to another page or screen quite easily.

As we’ve just said, the number of screens or pages may vary.

Let us take a look at the first page that opens us when we run our Flutter Application.

Bottom navigation bar custom theme with provider

As the favorite icon has been selected at the bottom navigation bar, and the first page is opened before us, we can easily differentiate two other views.

One of them is icon of places and the other is the icon of hotel.

Therefore, we can click the icon that represents places. And we see the following screenshot.

Bottom navigation bar customizing theme with provider
Bottom navigation bar customizing theme with provider

As the second page has been selected, the color changes from orange to yellow.

From the AppBar to the body text, to the bottom navigation bar, we’ve customized the whole color and text.

We’ve done that with the help of Provider package. Not only that, we’ve not used stateful widget to change the state of the item index.

To do that we’ve used a model class with Change notifier and, moreover, provided the value to the listener widgets.

If you have interest in reading the full code, please visit the respective GitHub Repository.

Let us take a look at the main file.

import 'package:flutter/material.dart';

import 'package:provider/provider.dart';

import 'models/element_at.dart';
import 'models/global_green_scheme.dart';

import 'views/app_bar_next.dart';
import 'views/first_page.dart';
import 'views/second_page.dart';
import 'views/third_page.dart';

void main() {
  runApp(
    /// Providers are above [Root App] instead of inside it, so that tests
    /// can use [Root App] while mocking the providers
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (cntext) => ElementAt(),
        ),
        Provider<GlobalGreenScheme>(
          create: (context) => GlobalGreenScheme(),
        ),
      ],
      child: const BottomNavigationBarTest(),
    ),
  );
}

class BottomNavigationBarTest extends StatelessWidget {
  const BottomNavigationBarTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final ThemeData globalTheme =
        Provider.of<GlobalGreenScheme>(context).globalTheme;
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: globalTheme,
      home: const BottomNavigationHome(),
    );
  }
}

class BottomNavigationHome extends StatelessWidget {
  const BottomNavigationHome({Key? key}) : super(key: key);

  static const TextStyle pageStyle = TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.bold,
  );

  static const List<Widget> _widgetOptions = <Widget>[
    FirstPage(),
    SecondPage(),
    ThirdPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Home Page',
          style: Theme.of(context).appBarTheme.titleTextStyle,
        ),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.add_alert),
            tooltip: 'Show Snackbar',
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('A SnackBar'),
                ),
              );
            },
          ),
          IconButton(
            icon: const Icon(Icons.search_outlined),
            tooltip: 'Search',
            onPressed: () {
              // our code
            },
          ),
          IconButton(
            icon: const Icon(Icons.navigate_next),
            tooltip: 'Next page',
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<void>(
                  builder: (BuildContext context) {
                    return const AppBarNext();
                  },
                ),
              );
            },
          ),
        ],
      ),
      body: Center(
        child: _widgetOptions.elementAt(context.watch<ElementAt>().count),
      ),
      bottomNavigationBar: BottomNavigationBar(
        /// customizing background color
        ///
        backgroundColor: Theme.of(context).bottomAppBarColor,
        mouseCursor: SystemMouseCursors.grab,
        unselectedItemColor: Colors.deepOrangeAccent,
        showSelectedLabels: false,
        showUnselectedLabels: false,
        type: BottomNavigationBarType.shifting,
        selectedFontSize: 20,
        selectedIconTheme: const IconThemeData(
          color: Colors.amberAccent,
        ),

        selectedLabelStyle: const TextStyle(
          fontWeight: FontWeight.bold,
        ),
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: const Icon(
              Icons.favorite_border_outlined,
            ),
            label: 'First Page',
            backgroundColor: Colors.green[200],
          ),
          BottomNavigationBarItem(
            icon: const Icon(
              Icons.place_outlined,
            ),
            label: 'Second Page',
            backgroundColor: Colors.green[400],
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.hotel_outlined),
            label: 'Third Page',
            backgroundColor: Colors.green[700],
          ),
        ],
        selectedItemColor: Colors.green[800],
        currentIndex: context.watch<ElementAt>().count,
        iconSize: 50,
        onTap: context.read<ElementAt>().onItemTapped,
        elevation: 5,
      ),
    );
  }
}

We’ve set a multi provider and two separate model classes to provide values to the listeners.

Consequently, in the model package in the lib folder, we have these two model classes. One of the model class provides global theme across our Flutter application.

import 'package:flutter/material.dart';

const Color customGreen50 = Color(0xffccff90);
const Color customGreen100 = Color(0xffa5d6a7);
const Color customGreen300 = Color(0xff76ff03);
const Color customGreen400 = Color(0xff9ccc65);

const Color customGreen900 = Color(0xff1b5a20);
const Color customGreen600 = Color(0xff8bc34a);

const Color customErrorRed = Color(0xFFC5032B);

const Color customSurfaceWhite = Color(0xFFFFFBFA);
const Color customBackgroundWhite = Colors.white;

class GlobalGreenScheme {
  final globalTheme = ThemeData(
    colorScheme: _customColorScheme,
    bottomAppBarColor: customGreen100,
    textTheme: const TextTheme(
      button: TextStyle(
        fontSize: 25,
        color: customGreen50,
        backgroundColor: customGreen900,
      ),
      bodyText1: TextStyle(
        fontSize: 22,
        color: customGreen600,
      ),
      bodyText2: TextStyle(
        color: customGreen400,
        fontSize: 18,
        fontWeight: FontWeight.bold,
        backgroundColor: customBackgroundWhite,
      ),
      caption: TextStyle(
        fontSize: 16,
        fontWeight: FontWeight.bold,
        fontStyle: FontStyle.italic,
        color: customGreen900,
        backgroundColor: customGreen50,
      ),
      headline1: TextStyle(
        color: customGreen900,
        fontSize: 50,
        fontFamily: 'Allison',
        fontWeight: FontWeight.bold,
      ),
      headline2: TextStyle(
        color: customGreen400,
        fontSize: 25,
        fontWeight: FontWeight.bold,
      ),
    ),
    appBarTheme: const AppBarTheme(
      backgroundColor: customGreen50,
      // This will control the "back" icon
      iconTheme: IconThemeData(color: Colors.red),
      // This will control action icon buttons that locates on the right
      actionsIconTheme: IconThemeData(color: customGreen900),
      centerTitle: false,
      elevation: 15,
      titleTextStyle: TextStyle(
        color: customGreen900,
        fontWeight: FontWeight.bold,
        fontFamily: 'Allison',
        fontSize: 40,
      ),
    ),
  );
}

const ColorScheme _customColorScheme = ColorScheme(
  primary: customGreen50,
  primaryVariant: customGreen600,
  secondary: Colors.amber,
  secondaryVariant: customGreen400,
  surface: Colors.purpleAccent,
  background: customSurfaceWhite,
  error: customGreen900,
  onPrimary: Colors.red,
  onSecondary: Colors.deepOrange,
  onSurface: customGreen300,
  onBackground: customGreen100,
  onError: Colors.redAccent,
  brightness: Brightness.light,
);

And the other model class helps us to change the item index belonging to the items of widgets.

import 'package:flutter/material.dart';

class ElementAt with ChangeNotifier {
  int count = 0;

  void onItemTapped(int index) {
    count = index;
    notifyListeners();
  }
}

We’ve kept three widgets in our view packages in the lib folder, so that we can navigate quite easily at the bottom navigation bar.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

3 responses to “How to customize bottom navigation bar”

  1. […] don’t assume that we cannot apply the same rule in Android. We can use the tabs at the bottom in Android in a different […]

  2. […] How to customize bottom navigation bar […]

  3. […] Firstly, let’s take a quick look at how we could use Bottom Navigation Bar within a Scaffold in Flutter.  […]

Leave a Reply