circle_bnb
This package allows to make a circled bottom navigation bar at the bottom of the screen.
Getting Started
Add this to your package's pubspec.yaml file:
dependencies:
circle_bnb: ^0.0.3
Usage
Then you just have to import the package with
import 'package:circle_bnb/circle_bnb.dart';
Properties
Here is a list of properties you can use to customize the CircleBNB widget:
size(Size): The size of the widget. If not provided, it will be calculated automatically.colorList(List<Color>?): The list of colors for the background gradient. Must contain 4 colors.dragSpeed(double): The speed of the dragging animation.items(List<CircleBNBItem>): The list of items to be displayed in the navigation bar. Must contain at least 3 items.onChangeIndex(Function(int index)): A callback function that is called when the selected index changes.navigationStyle(NavigationStyle): The style of the navigation bar. Can beNavigationStyle.linearorNavigationStyle.circular.linearItemCount(int?): The number of items to display whennavigationStyleisNavigationStyle.linear. Must be an odd number between 3 and 5.showIconWhenSelected(bool): Whether to show the icon of the selected item.showIconWhenUnselected(bool): Whether to show the icons of unselected items.showTextWhenSelected(bool): Whether to show the text of the selected item.showTextWhenUnselected(bool): Whether to show the text of unselected items.selectedIconColor(Color?): The color of the selected item's icon.selectedTextStyle(TextStyle?): The text style for the selected item's label.unselectedIconColor(Color?): The color of unselected items' icons.unselectedTextStyle(TextStyle?): The text style for unselected items' labels.circularBackgroundColor(Color?): The background color of the widget as a circular.linearBackgroundColor(Color?): The background color of the widget as a linear.
Example
import 'package:flutter/material.dart';
import 'package:circle_bnb/circle_bnb.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
highlightColor: Colors.transparent,
splashColor: Colors.blue.shade200,
),
home: const AppHomePage(),
);
}
}
class AppHomePage extends StatefulWidget {
const AppHomePage({super.key});
@override
State<AppHomePage> createState() => _AppHomePageState();
}
class _AppHomePageState extends State<AppHomePage> {
int _bnbIndex = 0;
NavigationStyle _navigationStyle = NavigationStyle.circular;
final List<CircleBNBItem> _items = [
CircleBNBItem(title: "Home", icon: Icons.home_outlined),
CircleBNBItem(title: "Dashboard", icon: Icons.dashboard_outlined),
CircleBNBItem(title: "Profile", icon: Icons.person_outlined),
CircleBNBItem(title: "Explore", icon: Icons.explore_outlined),
CircleBNBItem(title: "Settings", icon: Icons.settings_outlined),
CircleBNBItem(title: "Notifications", icon: Icons.notifications_outlined),
CircleBNBItem(title: "Saved", icon: Icons.bookmark_outline_outlined),
CircleBNBItem(title: "Favorites", icon: Icons.favorite_outline_outlined),
CircleBNBItem(title: "Search", icon: Icons.search_outlined),
CircleBNBItem(title: "Cart", icon: Icons.shopping_cart_outlined),
];
void setBnbIndex(int index) {
if (index != _bnbIndex) {
setState(() {
_bnbIndex = index;
});
}
}
void incrementItems() {
final newItem = CircleBNBItem(
title: 'Item ${_items.length + 1}',
icon: Icons.add_circle_outline,
);
setState(() {
_items.add(newItem);
_bnbIndex = 0;
});
}
void decrementItems() {
if (_items.length > 5) {
setState(() {
_items.removeLast();
if (_bnbIndex >= _items.length) {
_bnbIndex = _items.length - 1;
}
_bnbIndex = 0;
});
}
if (_items.length == 5) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Minimum 5 items required'),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_items[_bnbIndex].title),
centerTitle: true,
backgroundColor: Colors.primaries[_bnbIndex % Colors.primaries.length],
bottom: PreferredSize(
preferredSize: const Size(double.infinity, 96),
child: ColoredBox(
color: Colors.primaries[_bnbIndex % Colors.primaries.length],
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton.filledTonal(
icon: const Icon(Icons.exposure_minus_1),
onPressed: _items.length > 5 ? decrementItems : null,
style: IconButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'CircleBNBItem Count',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900,
fontWeight: FontWeight.bold
),
),
Text(
'${_items.length}',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900,
fontWeight: FontWeight.bold
),
),
],
),
IconButton.filledTonal(
icon: const Icon(Icons.exposure_plus_1),
onPressed: incrementItems,
style: IconButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
SizedBox(
width: 48,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'Linear',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900,
fontWeight: _navigationStyle == NavigationStyle.linear
? FontWeight.bold
: FontWeight.normal
),
),
),
),
Switch(
value: _navigationStyle == NavigationStyle.circular,
onChanged: (value) {
setState(() {
_navigationStyle = value ? NavigationStyle.circular : NavigationStyle.linear;
});
},
activeColor: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900,
inactiveThumbColor: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900,
inactiveTrackColor: Colors.white.withOpacity(0.75),
),
SizedBox(
width: 48,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'Circular',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.primaries[_bnbIndex % Colors.primaries.length].shade900,
fontWeight: _navigationStyle == NavigationStyle.circular
? FontWeight.bold
: FontWeight.normal
),
),
),
),
],
),
],
),
),
),
),
body: IndexedStack(
index: _bnbIndex,
children: List.generate(
_items.length,
(index) => ListView(
children: List.generate(
_items.length,
(index2) => Container(
color: Colors.primaries[(index + index2) % Colors.primaries.length],
height: MediaQuery.of(context).size.height * 0.5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_items.length,
(index3) => Text(
_items[index3].title,
style: TextStyle(color: index3 == _bnbIndex ? Colors.white : Colors.white24),
),
),
),
),
),
),
),
),
extendBody: true,
backgroundColor: Colors.primaries[_bnbIndex % Colors.primaries.length],
bottomNavigationBar: CircleBNB(
key: ValueKey('${_items.length}_$_navigationStyle'),
navigationStyle: _navigationStyle,
linearItemCount: 5,
dragSpeed: 0.05,
items: _items,
colorList: const [
Color(0xFFFF9B54),
Color(0xFFFF7F51),
Color(0xFFCE4257),
Color(0xFF720026),
],
linearBackgroundColor: const Color(0xFFFF9B54),
selectedIconColor: Colors.black87,
unselectedIconColor: Colors.black38,
selectedTextStyle: const TextStyle(
color: Color(0xFF4F000B),
fontWeight: FontWeight.bold,
),
unselectedTextStyle: const TextStyle(
color: Colors.black38,
fontWeight: FontWeight.normal,
),
onChangeIndex: setBnbIndex,
),
);
}
}
Example - Video
- navigationStyle: NavigationStyle.circular
https://github.com/user-attachments/assets/dd3425b9-54a9-4a5e-8253-f90c0e7d5188
- navigationStyle: NavigationStyle.linear
https://github.com/user-attachments/assets/ce3245d4-3f42-4741-afcb-dd9356e3cf2a