Creating a Scrollable Bottom Navigation Bar in Flutter

Walnut Software
7 min readSep 4, 2023

--

In this article, I will guide you through the fundamental development process of a bottom navigation bar in Flutter using the scrollable_reorderable_navbar package. This development will enable users to personalize and use the navigation bar. Users will be able to move the bar left and right and delete the icons they have selected here.

Setting up the project

Open a new terminal. Navigate to the directory where you want to create a new Flutter project using terminal commands. Then, use the following command to create a new Flutter project:

flutter create example_app

You can replace “example_app” with the name of the project you want to create.

Building the UI

At the initial stage, I am not adding the package directly to my dependencies (pubspec.yaml) because I won’t use the package as it is; instead, I will make modifications to it. Therefore, I will copying the folders and files in the package into the lib folder in the Flutter project.

In the local lib, I will add some files to visualize the basic interface and explore what changes we can make to it.

The sample code directory on the page of the package in pub.dev is as follows, we will make changes to it first.

import 'package:nav_bar/scrollable_reorderable_navbar.dart';
import 'package:flutter/material.dart';

class BottomNavBarPage extends StatefulWidget {
const BottomNavBarPage({Key? key}) : super(key: key);

@override
_BottomNavBarPageState createState() => _BottomNavBarPageState();
}
class _BottomNavBarPageState extends State<BottomNavBarPage> {
int _selectedIndex = 0;
List<NavBarItem> _items = const [
NavBarItem(widget: Icon(Icons.home), name: "Home"),
NavBarItem(widget: Icon(Icons.group), name: "Social"),
NavBarItem(widget: Icon(Icons.call), name: "Calls"),
NavBarItem(widget: Icon(Icons.image), name: "Pictures"),
NavBarItem(widget: Icon(Icons.message), name: "Messages"),
NavBarItem(widget: Icon(Icons.settings), name: "Settings")
];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Bottom nav bar")),
bottomNavigationBar: ScrollableReorderableNavBar(
onItemTap: (arg) {
setState(() {
_selectedIndex = arg;
});
},
onReorder: (oldIndex, newIndex) {
final currItem = _items[_selectedIndex];
if (oldIndex < newIndex) newIndex -= 1;
final newItems = [..._items], item = newItems.removeAt(oldIndex);
newItems.insert(newIndex, item);
setState(() {
_items = newItems;
_selectedIndex = _items.indexOf(currItem);
});
},
items: _items,
selectedIndex: _selectedIndex,
onDelete: (index) => setState(() => _items.removeAt(index)),
deleteIndicationWidget: Container(
padding: const EdgeInsets.only(bottom: 100),
child: Align(
alignment: Alignment.bottomCenter,
child: Wrap(
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
Text("Tap on nav item to delete it.",
style: Theme.of(context).textTheme.bodyText1,
textAlign: TextAlign.center),
TextButton(onPressed: () {}, child: const Text("DONE"))
],
),
),
),
),
);
}
}

Modifications

In the basic code, icons from the Flutter package are used. To use different icons, first, you need to download the icons you want to use. Afterward, create an “images” folder using terminal commands. You place these icons you want to use in the “images” folder within your project. To access these icons in the folder, you need to make the following changes in the pubspec.yaml file:

  1. Locate the pubspec.yaml file.
  2. Find the assets line and remove the '#' symbol in front of 'assets'.
  3. Add ‘ — images/ ‘ under the ‘assets’ line.
  4. Save the file by pressing Ctrl+S.
flutter:
assets:
- images/

To see different icons within the navigation bar, we need to revise the code. First, we create a main.dart file, and the code should look like the following:

import 'package:flutter/material.dart';
import 'package:nav_bar/scrollable_reorderable_navbar.dart';

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

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

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,

theme: ThemeData(
primarySwatch: Colors.red,
),
home: const BottomNavBarPage(),
);
}
}

We will create a different Dart file to display icons in the navbar. To create the Dart file, navigate to the folder where you want to create it, right-click, select the “New File” option, then enter the name for the Dart file, making sure to add “.dart” at the end. In my case, the name of the Dart file is “bottom_nav_bar.dart.

When creating a new Dart file in this project, you should always export it to the “scrollable_reorderable_navbar.dart” file. The main purpose of this is to simplify the process in projects with a large number of Dart files. Instead of importing all Dart files one by one, exporting it via “scrollable_reorderable_navbar.dart” when you create a new Dart file will allow you to import all other Dart files as needed, depending on the usage of the code.

library scrollable_reorderable_navbar;

export 'Components/models/nav_bar_item.dart';
export 'Components/types/types.dart';
export 'Components/widgets/condition_widget.dart';
export 'Components/widgets/delete_mode_item_listener.dart';
export 'Components/widgets/reorderable_item.dart';
export 'Components/widgets/scrollable_reorderable_navbar.dart';
export 'Components/bottom_nav_bar.dart';

In the last line, we are exporting our created Dart file by specifying its path. The crucial point to note here is that I organize my files and folders within the “Components” directory. You should update the path accordingly based on the folder in which you have created the file.

● After copying the provided code as an example into our created Dart file, we will make the following changes to the _items list in order to display our downloaded PNG files on the bottom navigation bar.

List<NavBarItem> _items = [
NavBarItem(widget: Image.asset("images/phone-call.png",height: 30,), name: 'Phone Call'),
NavBarItem(widget: Image.asset("images/envelope.png",height: 30,), name: 'Mail'),
NavBarItem(widget: Image.asset("images/heart.png",height: 30,), name: 'Fav'),
NavBarItem(widget: Image.asset("images/home.png",height: 30,), name: 'Home'),
NavBarItem(widget: Image.asset("images/marker.png",height: 30,), name: 'Location'),
NavBarItem(widget: Image.asset("images/user.png",height: 30,), name: 'User'),
NavBarItem(widget: Image.asset("images/youtube.png",height: 30,), name: 'Youtube'),
NavBarItem(widget: Image.asset("images/twitter.png",height: 30,), name: 'Twitter'),

];

Now that we can correctly load the icons, we can make the necessary code adjustments to place a fixed icon in the bottom left corner. The scrollable navigation bar displays 5 icons on the screen, and I want to add one fixed icon. To achieve this, I’ll create a box that takes up 1/5 of the screen for the single icon and 4/5 for the scrollable navigation bar. To add the icon to the 1/5 portion at the bottom left, I’ll make the required changes.

Widget build(BuildContext context) {

return Stack(
children: <Widget>[
Positioned(
left: 0,
bottom: 0,
width: MediaQuery.of(context).size.width / 5,
height: kBottomNavigationBarHeight + MediaQuery.of(context).viewPadding.bottom,
child: Container(
decoration: decoration ?? BoxDecoration(
color: Colors.white,
boxShadow: const [ BoxShadow(
color: Colors.black12, offset: Offset(0, -1), blurRadius: 0,spreadRadius: 0,)
],
),child: centerWidget ?? SizedBox(),
),
),
Positioned(
left: MediaQuery.of(context).size.width / 5,
bottom: 0,
width: MediaQuery.of(context).size.width * 4 / 5,
height: kBottomNavigationBarHeight + MediaQuery.of(context).viewPadding.bottom,
child: Container(
decoration: decoration ?? BoxDecoration(
color: Colors.white,
boxShadow: const [ BoxShadow(
color: Colors.black12, offset: Offset(0, -1), blurRadius: 0,spreadRadius: 0,)
],
),
child: _ScrollableReorderableNavBar(
selectedIndex: selectedIndex,
items: items,
backgroundColor: backgroundColor,
onItemTap: onItemTap,
animationDuration: duration,
onReorder: onReorder,
proxyDecorator: proxyDecorator,
onDelete: onDelete,
deleteIndicationWidget: deleteIndicationWidget,
),
),
),
],
);
}

I have made changes to the “Widget build(BuildContext context)” structure in the “scrollable_reorderable_navbar.dart” file of the “scrollable_reorderable_navbar” package. The line “child: centerWidget ?? SizedBox(),” is a feature that is provided to add an icon from outside the box.

If you are familiar with how to define this feature, you can skip to the next step. If you are not familiar, please follow the next step.

How to define a feature ? (Optional)

const ScrollableReorderableNavBar(
{Key? key,
this.centerWidget,
//other commands...
})
: super(key: key);
final Widget? centerWidget;
//other commands...

● To display an icon inside the bottom-left corner box and make it change color when clicked, you will need to make a few modifications to the code. You can achieve this in two different ways:

First, by swapping between two different PNG images,

Second, by altering the color of a single PNG image.

I used the first method in terms of code structure, switching between two different PNG images. Here’s how you can do it:

class _BottomNavBarPageState extends State<BottomNavBarPage> {
int _selectedIndex = -1;
//To prevent the animation from being active when the application is
//opened for the first time, this value was set to -1
String currentImage = "images/appsBlack.png";
//The initial assignment for the black-colored icon to appear
//when the application is first opened
//other commands...
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: ScrollableReorderableNavBar(
centerWidget: GestureDetector(
onTap: (){
_selectedIndex = -1;
setState(() {
if(currentImage == "images/appsBlack.png"){
currentImage = "images/appsBlue.png";
}
});
},
child: Image.asset(currentImage),
),

onItemTap: (arg) {
setState(() {
_selectedIndex = arg;
currentImage = "images/appsBlack.png";
});
},
//other commands...
);
}

Using the centerWidget property defined earlier, when the user clicks on this icon through the GestureDetector, the onTap function runs. Inside this function, if the clicked icon is black in color, the setState method is used to change currentImage to an icon with a blue color.

On the other hand, the onItemTap function, located outside of GestureDetector, runs when icons inside a 4/5 aspect ratio box are clicked. Inside it, the setState method is used to change these icons to black in color when clicked. With this change, the currentImage is returned to the box inside GestureDetector using "child: Image.asset(currentImage)".

In summary, when the user clicks on the icon within GestureDetector, the onTap function changes the color of the icon to blue if it was originally black. When icons inside a 4/5 aspect ratio box are clicked, the onItemTap function changes the color of these icons to black, and this change is reflected by updating currentImage inside the GestureDetector to display the selected icon.

● The package was initially configured to display 5 icons in the navigation bar, and the clicked icon was supposed to move to the 3rd position to appear in the middle of the screen. Now that there are 4 scrollable icons, we need to change this. Additionally, there was originally some left margin space for the icons to align, but due to adding a box in the bottom-left corner, we will reduce this margin to zero.

//before the changes
void _centerFocusItem(int index) {
final preferredX = _cellSize * (index - 2),
gap = _items.length > _maxItemDisplayed ? _cellSize / 2 : 0,
//other commands...
}
//after the changes
void _centerFocusItem(int index) {
final preferredX = _cellSize * (index - 1),
gap = _items.length > _maxItemDisplayed ? 0 : 0,
//other commands...
}

WHAT’S NEXT

A screen that will appear when the application is opened in the future can be created. An update can be made to include page paths in the _items so that after page paths are entered into the _items list, the icon that is clicked can open its own page. The redirection to a page that will open when the Apps icon is clicked can be done within the onTap function.

Happy coding!

--

--