How to use BottomNavigationBar with Navigator?
Solution 1
int index = 0;
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
new Offstage(
offstage: index != 0,
child: new TickerMode(
enabled: index == 0,
child: new MaterialApp(home: new YourLeftPage()),
),
),
new Offstage(
offstage: index != 1,
child: new TickerMode(
enabled: index == 1,
child: new MaterialApp(home: new YourRightPage()),
),
),
],
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: index,
onTap: (int index) { setState((){ this.index = index; }); },
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text("Left"),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.search),
title: new Text("Right"),
),
],
),
);
}
You should keep each page by Stack
to keep their state.
Offstage
stops painting, TickerMode
stops animation.
MaterialApp
includes Navigator
.
Solution 2
Output:
Code:
int _index = 0;
@override
Widget build(BuildContext context) {
Widget child;
switch (_index) {
case 0:
child = FlutterLogo();
break;
case 1:
child = FlutterLogo(colors: Colors.orange);
break;
case 2:
child = FlutterLogo(colors: Colors.red);
break;
}
return Scaffold(
body: SizedBox.expand(child: child),
bottomNavigationBar: BottomNavigationBar(
onTap: (newIndex) => setState(() => _index = newIndex),
currentIndex: _index,
items: [
BottomNavigationBarItem(icon: Icon(Icons.looks_one), title: Text("Blue")),
BottomNavigationBarItem(icon: Icon(Icons.looks_two), title: Text("Orange")),
BottomNavigationBarItem(icon: Icon(Icons.looks_3), title: Text("Red")),
],
),
);
}
Solution 3
The Complete Example
First make a class MyBottomBarDemo
class MyBottomBarDemo extends StatefulWidget {
@override
_MyBottomBarDemoState createState() => new _MyBottomBarDemoState();
}
class _MyBottomBarDemoState extends State<MyBottomBarDemo> {
int _pageIndex = 0;
PageController _pageController;
List<Widget> tabPages = [
Screen1(),
Screen2(),
Screen3(),
];
@override
void initState(){
super.initState();
_pageController = PageController(initialPage: _pageIndex);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("BottomNavigationBar", style: TextStyle(color: Colors.white)),
backgroundColor: Colors.deepPurple,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _pageIndex,
onTap: onTabTapped,
backgroundColor: Colors.white,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("Home")),
BottomNavigationBarItem(icon: Icon(Icons.mail), title: Text("Messages")),
BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("Profile")),
],
),
body: PageView(
children: tabPages,
onPageChanged: onPageChanged,
controller: _pageController,
),
);
}
void onPageChanged(int page) {
setState(() {
this._pageIndex = page;
});
}
void onTabTapped(int index) {
this._pageController.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
}
}
Then create a your screens
class Screen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Center(child: Text("Screen 1")),
);
}
}
class Screen2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
child: Center(child: Text("Screen 2")),
);
}
}
class Screen3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.cyan,
child: Center(child: Text("Screen 3")),
);
}
}
Solution 4
Here is an example how you can use Navigator with BottomNavigationBar to navigate different screen.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// This navigator state will be used to navigate different pages
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
int _currentTabIndex = 0;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Navigator(key: _navigatorKey, onGenerateRoute: generateRoute),
bottomNavigationBar: _bottomNavigationBar(),
),
);
}
Widget _bottomNavigationBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text("Account")),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text("Settings"),
)
],
onTap: _onTap,
currentIndex: _currentTabIndex,
);
}
_onTap(int tabIndex) {
switch (tabIndex) {
case 0:
_navigatorKey.currentState.pushReplacementNamed("Home");
break;
case 1:
_navigatorKey.currentState.pushReplacementNamed("Account");
break;
case 2:
_navigatorKey.currentState.pushReplacementNamed("Settings");
break;
}
setState(() {
_currentTabIndex = tabIndex;
});
}
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case "Account":
return MaterialPageRoute(builder: (context) => Container(color: Colors.blue,child: Center(child: Text("Account"))));
case "Settings":
return MaterialPageRoute(builder: (context) => Container(color: Colors.green,child: Center(child: Text("Settings"))));
default:
return MaterialPageRoute(builder: (context) => Container(color: Colors.white,child: Center(child: Text("Home"))));
}
}
}
Solution 5
Here is example:
int _currentIndex = 0;
Route<Null> _getRoute(RouteSettings settings) {
final initialSettings = new RouteSettings(
name: settings.name,
isInitialRoute: true);
return new MaterialPageRoute<Null>(
settings: initialSettings,
builder: (context) =>
new Scaffold(
body: new Center(
child: new Container(
height: 200.0,
width: 200.0,
child: new Column(children: <Widget>[
new Text(settings.name),
new FlatButton(onPressed: () =>
Navigator.of(context).pushNamed(
"${settings.name}/next"), child: new Text("push")),
],
))
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (value) {
final routes = ["/list", "/map"];
_currentIndex = value;
Navigator.of(context).pushNamedAndRemoveUntil(
routes[value], (route) => false);
},
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.list), title: new Text("List")),
new BottomNavigationBarItem(
icon: new Icon(Icons.map), title: new Text("Map")),
]),
));
}
@override
Widget build(BuildContext context) =>
new MaterialApp(
initialRoute: "/list",
onGenerateRoute: _getRoute,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
);
You can set isInitialRoute to true and pass it to MaterialPageRoute. It will remove pop animation.
And to remove old routes you can use pushNamedAndRemoveUntil
Navigator.of(context).pushNamedAndRemoveUntil(routes[value], (route) => false);
To set current page you can have a variable in your state _currentIndex
and assign it to BottomNavigationBar:
Related videos on Youtube
Paul
Updated on February 19, 2022Comments
-
Paul about 2 years
The Flutter Gallery example of
BottomNavigationBar
uses aStack
ofFadeTransitions
in the body of theScaffold
.I feel it would be cleaner (and easier to animate) if we could switch pages by using a
Navigator
.Are there any examples of this?
-
Paul almost 7 yearsInteresting. Is the BottomNavigationBar sticking around throughout navigation changes? I'll need it to also reflect the current page.
-
German Saprykin almost 7 yearsBottomNavigationBar is not sticking, roughly we create every time new one, but with right state.
-
German Saprykin almost 7 yearsUpdated example with current index
-
CodeGrue about 6 yearscan you please elaborate on the purpose of the TickerMode widgets? This code seems to work fine also: new Offstage( offstage: _tabIndex != 1, child: new YourRightPage(), ),
-
RafaelHamasaki about 6 yearsWith this implementation, the back button doesn't work for accessing the previous Offstage widget. Is there any way to support this?
-
najeira about 6 years@RafaelHamasaki You can handle back button using docs.flutter.io/flutter/widgets/WidgetsBindingObserver/…
-
najeira about 6 years@CodeGrue I use TickerMode to stop the animation of the widgets which are not displayed.
-
RafaelHamasaki about 6 years@najeira Yeah, that's what I ended up doing. Too bad I have to keep my own "navigation" history. Thanks anyway!
-
camino about 6 years@najeira it seems that the code above only support 2 BottomNavigationBarItem, if I add one more item, it give an error in the simulator: RangeError(index) invalid value:Not in range 0..1, do you know why?
-
najeira about 6 years@camino You should to add both BottomNavigationBarItem and a child of Stack.
-
CodeGrue about 6 yearsYou can have multiple nested MaterialApp widgets? (assuming this example is itself inside a parent MaterialApp)
-
najeira about 6 years@CodeGrue Yes, you can.
-
Kirill Karmazin over 4 yearscould you please elaborate more on this line:
body: Navigator(key: _navigatorKey, onGenerateRoute: generateRoute),
is it ok to set a Navigator widget as a body of your app? Looks strange. How does it work? -
ThemBones over 4 yearsThere are one or two highly complicated tutorials on this topic, containing flaws which render them basically useless. This short snippet was the only one that works. The catch is that you need to pass the pushNamed to the navigator key. Thank you, great work.
-
Zakir over 4 years@KirillKarmazin If you want to navigate between different widgets. You have to put them under a navigator in widget tree. Since in this example we only want to change the body of the screen during navigation. So I add the navigator as the top widget in body and put other widget under it.
-
ych over 4 yearsUnfortunately, as I understand, your solution works only if the Scaffold body is the same widget but with different properties, which is not a regular case.
-
Wail Hayaly about 4 yearsI'm using this method with flutter_bloc it's GREAT, But when the app begins it loads the whole stacked-widgets data from the API, How can I make it load only the viewed stack widget when the user view it for the first time?
-
publicJorn about 4 yearsAn alternative may be IndexedStack, which provides a simpler API to
Stack
combined withOffstage
. You may still needTickerMode
though! -
Gulnaz Ghanchi almost 4 yearsthis helped me a lot with simple implementation. thanks a lot
-
Shakeel Ahmad almost 4 yearsVery well explained. only extra I did to use ChangeNotifier Provider for selected tab index. This way even if I route to page(bottom bar pages) from within the page, it activate the selected index.
-
Lee Probert almost 4 yearsPushReplacementNamed is Going to purge the last screen in the stack. You need all the sections in your navigation to exist simultaneously with their own navigators intact. The only way I’ve found to do this is with an IndexedStack but I don’t use a Navigator. Still looking for a good solution.
-
Amanpreet Kaur over 3 yearsThis is simple and great it would be great if you can help me with handing backpressed in this
-
CopsOnRoad over 3 years@AmanpreetKaur For that you can use
Set
. On press of a new item, simply add its index to the set, wrap your widget inWillPopScope
and insideonWillPop
you can check the value of last index, if it's empty, simply exit the app else set the current index to that value. -
Jessica over 3 yearsSay I'm on the home page and I want to go to the single page of the messages. How can I go to that route on a button click in the home page?
-
Nagabhushan Baddi over 3 years@publicJorn Are IndexedStack and Stack+Offstage equivalent in terms of performance?
-
G H Prakash over 2 yearsnice.. it helps me a lot