Line data Source code
1 : import 'package:flutter/material.dart';
2 :
3 : /// Represents an individual navigation item within the [CustomBottomNavBar].
4 : ///
5 : /// The [NavigationItem] class encapsulates the information required for each
6 : /// item in the bottom navigation bar, including the target page, icon, and label.
7 : class NavigationItem {
8 : /// The page to navigate to when this item is selected.
9 : ///
10 : /// This should be a widget that represents the content associated with the navigation item.
11 : final Widget page;
12 :
13 : /// The icon to display for this navigation item.
14 : ///
15 : /// Typically, this is an [Icon] widget that visually represents the item's purpose.
16 : final Icon icon;
17 :
18 : /// The label text for this navigation item.
19 : ///
20 : /// This text appears below the icon and provides a descriptive name for the item.
21 : final String label;
22 :
23 : /// Creates a [NavigationItem] with the specified [page], [icon], and [label].
24 : ///
25 : /// All parameters are required and must not be null.
26 1 : const NavigationItem({
27 : required this.page,
28 : required this.icon,
29 : required this.label,
30 : });
31 : }
32 :
33 : /// A customizable bottom navigation bar that manages navigation between different pages.
34 : ///
35 : /// The [CustomBottomNavBar] widget provides a styled bottom navigation bar that allows
36 : /// users to switch between various pages in the app. It integrates seamlessly with a
37 : /// [PageView] to handle page transitions and maintains the selected index state.
38 : ///
39 : /// **Example usage:**
40 : /// ```dart
41 : /// List<NavigationItem> navItems = [
42 : /// NavigationItem(
43 : /// page: HomePage(),
44 : /// icon: Icon(Icons.home),
45 : /// label: 'Home',
46 : /// ),
47 : /// NavigationItem(
48 : /// page: SearchPage(),
49 : /// icon: Icon(Icons.search),
50 : /// label: 'Search',
51 : /// ),
52 : /// NavigationItem(
53 : /// page: ProfilePage(),
54 : /// icon: Icon(Icons.person),
55 : /// label: 'Profile',
56 : /// ),
57 : /// ];
58 : ///
59 : /// CustomBottomNavBar(
60 : /// items: navItems,
61 : /// backgroundColor: Colors.white,
62 : /// selectedItemColor: Colors.blue,
63 : /// unselectedItemColor: Colors.grey,
64 : /// elevation: 10.0,
65 : /// type: BottomNavigationBarType.fixed,
66 : /// );
67 : /// ```
68 : class CustomBottomNavBar extends StatefulWidget {
69 : /// A list of [NavigationItem]s to display in the bottom navigation bar.
70 : ///
71 : /// Each [NavigationItem] defines a page, icon, and label for a navigation tab.
72 : final List<NavigationItem> items;
73 :
74 : /// The background color of the bottom navigation bar.
75 : ///
76 : /// If not specified, it defaults to the theme's `colorScheme.surface`.
77 : final Color? backgroundColor;
78 :
79 : /// The color of the selected navigation item.
80 : ///
81 : /// If not specified, it defaults to the theme's `colorScheme.primary`.
82 : final Color? selectedItemColor;
83 :
84 : /// The color of the unselected navigation items.
85 : ///
86 : /// If not specified, it defaults to the theme's `unselectedWidgetColor`.
87 : final Color? unselectedItemColor;
88 :
89 : /// The elevation (shadow depth) of the bottom navigation bar.
90 : ///
91 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.elevation`
92 : /// or `8.0` if the theme does not provide one.
93 : final double? elevation;
94 :
95 : /// The type of the bottom navigation bar.
96 : ///
97 : /// Determines the layout behavior of the items. If not specified, it defaults
98 : /// to the theme's `bottomNavigationBarTheme.type` or `BottomNavigationBarType.shifting`.
99 : final BottomNavigationBarType? type;
100 :
101 : /// The font size of the selected item's label.
102 : ///
103 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.selectedLabelStyle.fontSize`
104 : /// or `14.0`.
105 : final double? selectedFontSize;
106 :
107 : /// The font size of the unselected items' labels.
108 : ///
109 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.unselectedLabelStyle.fontSize`
110 : /// or `12.0`.
111 : final double? unselectedFontSize;
112 :
113 : /// Whether to show labels for the selected items.
114 : ///
115 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.showSelectedLabels`
116 : /// or `true`.
117 : final bool? showSelectedLabels;
118 :
119 : /// Whether to show labels for the unselected items.
120 : ///
121 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.showUnselectedLabels`
122 : /// or `true`.
123 : final bool? showUnselectedLabels;
124 :
125 : /// The icon theme for the selected navigation item.
126 : ///
127 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.selectedIconTheme`
128 : /// or an [IconThemeData] with size `24.0`.
129 : final IconThemeData? selectedIconTheme;
130 :
131 : /// The icon theme for the unselected navigation items.
132 : ///
133 : /// If not specified, it defaults to the theme's `bottomNavigationBarTheme.unselectedIconTheme`
134 : /// or an [IconThemeData] with size `20.0`.
135 : final IconThemeData? unselectedIconTheme;
136 :
137 : /// The padding to apply around each navigation item's icon.
138 : ///
139 : /// If not specified, it defaults to `EdgeInsets.zero`.
140 : final EdgeInsetsGeometry? itemPadding;
141 :
142 : /// The external margin around the bottom navigation bar.
143 : ///
144 : /// If not specified, it defaults to `EdgeInsets.all(0)`.
145 : final EdgeInsetsGeometry? margin;
146 :
147 : /// Creates a [CustomBottomNavBar] with the specified properties.
148 : ///
149 : /// The [items] parameter is required and must contain at least two [NavigationItem]s.
150 1 : const CustomBottomNavBar({
151 : super.key,
152 : required this.items,
153 : this.backgroundColor,
154 : this.selectedItemColor,
155 : this.unselectedItemColor,
156 : this.elevation,
157 : this.type,
158 : this.selectedFontSize,
159 : this.unselectedFontSize,
160 : this.showSelectedLabels,
161 : this.showUnselectedLabels,
162 : this.selectedIconTheme,
163 : this.unselectedIconTheme,
164 : this.itemPadding,
165 : this.margin,
166 : });
167 :
168 1 : @override
169 1 : State<CustomBottomNavBar> createState() => _CustomBottomNavBarState();
170 : }
171 :
172 : class _CustomBottomNavBarState extends State<CustomBottomNavBar> {
173 : /// The index of the currently selected navigation item.
174 : ///
175 : /// Initialized to `0`, representing the first item in the [widget.items] list.
176 : int _selectedIndex = 0;
177 :
178 : /// Controller for managing page transitions in the [PageView].
179 : ///
180 : /// Prevents users from swiping between pages manually by setting
181 : /// [physics] to [NeverScrollableScrollPhysics].
182 : PageController pageController = PageController();
183 :
184 : /// Handles taps on navigation items.
185 : ///
186 : /// Animates the [PageView] to the selected page and updates the [_selectedIndex].
187 1 : void _onItemTapped(int index) {
188 2 : pageController.animateToPage(
189 : index,
190 : duration: const Duration(milliseconds: 500),
191 : curve: Curves.ease,
192 : );
193 2 : setState(() {
194 1 : _selectedIndex = index;
195 : });
196 : }
197 :
198 1 : @override
199 : Widget build(BuildContext context) {
200 : // Retrieve the current theme's BottomNavigationBarThemeData.
201 2 : final bottomNavBarTheme = Theme.of(context).bottomNavigationBarTheme;
202 :
203 1 : return Scaffold(
204 : // The main content area that displays the selected page.
205 1 : body: PageView(
206 1 : controller: pageController,
207 : physics: const NeverScrollableScrollPhysics(),
208 : // Disables swipe navigation.
209 6 : children: widget.items.map((e) => e.page).toList(),
210 : ),
211 : // The bottom navigation bar wrapped in a styled Card.
212 1 : bottomNavigationBar: Card(
213 2 : margin: widget.margin ?? const EdgeInsets.all(0),
214 3 : elevation: widget.elevation ?? bottomNavBarTheme.elevation ?? 8.0,
215 2 : color: widget.backgroundColor ??
216 1 : bottomNavBarTheme.backgroundColor ??
217 3 : Theme.of(context).colorScheme.surface,
218 : shape: const RoundedRectangleBorder(
219 : borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
220 : ),
221 1 : child: ClipRRect(
222 : borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
223 : clipBehavior: Clip.hardEdge,
224 1 : child: BottomNavigationBar(
225 : elevation: 0,
226 : backgroundColor: Colors.transparent,
227 : // Allows Card's color to show.
228 2 : type: widget.type ??
229 1 : bottomNavBarTheme.type ??
230 : BottomNavigationBarType.shifting,
231 2 : selectedItemColor: widget.selectedItemColor ??
232 1 : bottomNavBarTheme.selectedItemColor ??
233 3 : Theme.of(context).colorScheme.primary,
234 2 : unselectedItemColor: widget.unselectedItemColor ??
235 1 : bottomNavBarTheme.unselectedItemColor ??
236 2 : Theme.of(context).unselectedWidgetColor,
237 2 : selectedFontSize: widget.selectedFontSize ??
238 1 : bottomNavBarTheme.selectedLabelStyle?.fontSize ??
239 : 14.0,
240 2 : unselectedFontSize: widget.unselectedFontSize ??
241 1 : bottomNavBarTheme.unselectedLabelStyle?.fontSize ??
242 : 12.0,
243 2 : showSelectedLabels: widget.showSelectedLabels ??
244 1 : bottomNavBarTheme.showSelectedLabels ??
245 : true,
246 2 : showUnselectedLabels: widget.showUnselectedLabels ??
247 1 : bottomNavBarTheme.showUnselectedLabels ??
248 : true,
249 2 : selectedIconTheme: widget.selectedIconTheme ??
250 1 : bottomNavBarTheme.selectedIconTheme ??
251 : const IconThemeData(size: 24.0),
252 2 : unselectedIconTheme: widget.unselectedIconTheme ??
253 1 : bottomNavBarTheme.unselectedIconTheme ??
254 : const IconThemeData(size: 20.0),
255 4 : items: widget.items.map((item) {
256 1 : return BottomNavigationBarItem(
257 1 : icon: Container(
258 2 : padding: widget.itemPadding ?? EdgeInsets.zero,
259 : alignment: Alignment.center,
260 1 : child: item.icon,
261 : ),
262 1 : label: item.label,
263 : );
264 1 : }).toList(),
265 1 : currentIndex: _selectedIndex,
266 1 : onTap: _onItemTapped,
267 : ),
268 : ),
269 : ),
270 : );
271 : }
272 : }
|