Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
192 lines (170 sloc) 6.18 KB
import 'package:adaptive_bottom_nav_sample/custom/bottom_navigation_tab.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
/// A Scaffold with a configured BottomNavigationBar, separate
/// Navigators for each tab view and state retaining across tab switches.
class MaterialBottomNavigationScaffold extends StatefulWidget {
const MaterialBottomNavigationScaffold({
@required this.navigationBarItems,
@required this.onItemSelected,
@required this.selectedIndex,
Key key,
}) : assert(navigationBarItems != null),
assert(onItemSelected != null),
assert(selectedIndex != null),
super(key: key);
/// List of the tabs to be displayed with their respective navigator's keys.
final List<BottomNavigationTab> navigationBarItems;
/// Called when a tab selection occurs.
final ValueChanged<int> onItemSelected;
final int selectedIndex;
_MaterialBottomNavigationScaffoldState createState() =>
class _MaterialBottomNavigationScaffoldState
extends State<MaterialBottomNavigationScaffold>
with TickerProviderStateMixin<MaterialBottomNavigationScaffold> {
final List<_MaterialBottomNavigationTab> materialNavigationBarItems = [];
final List<AnimationController> _animationControllers = [];
/// Controls which tabs should have its content built. This enables us to
/// lazy instantiate it.
final List<bool> _shouldBuildTab = <bool>[];
void initState() {
void _initMaterialNavigationBarItems() {
(barItem) => _MaterialBottomNavigationTab(
bottomNavigationBarItem: barItem.bottomNavigationBarItem,
navigatorKey: barItem.navigatorKey,
subtreeKey: GlobalKey(),
initialPageBuilder: barItem.initialPageBuilder,
void _initAnimationControllers() {
(destination) => AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
if (_animationControllers.isNotEmpty) {
_animationControllers[0].value = 1.0;
void dispose() {
(controller) => controller.dispose(),
Widget build(BuildContext context) => Scaffold(
// The Stack is what allows us to retain state across tab
// switches by keeping all of our views in the widget tree.
body: Stack(
fit: StackFit.expand,
children: materialNavigationBarItems
(barItem) => _buildPageFlow(
bottomNavigationBar: BottomNavigationBar(
currentIndex: widget.selectedIndex,
items: materialNavigationBarItems
(item) => item.bottomNavigationBarItem,
onTap: widget.onItemSelected,
// The best practice here would be to extract this to another Widget,
// however, moving it to a separate class would only harm the
// readability of our guide.
Widget _buildPageFlow(
BuildContext context,
int tabIndex,
_MaterialBottomNavigationTab item,
) {
final isCurrentlySelected = tabIndex == widget.selectedIndex;
// We should build the tab content only if it was already built or
// if it is currently selected.
_shouldBuildTab[tabIndex] =
isCurrentlySelected || _shouldBuildTab[tabIndex];
final Widget view = FadeTransition(
opacity: _animationControllers[tabIndex].drive(
CurveTween(curve: Curves.fastOutSlowIn),
child: KeyedSubtree(
key: item.subtreeKey,
child: _shouldBuildTab[tabIndex]
? Navigator(
// The key enables us to access the Navigator's state inside the
// onWillPop callback and for emptying its stack when a tab is
// re-selected. That is why a GlobalKey is needed instead of
// a simpler ValueKey.
key: item.navigatorKey,
// Since this isn't the purpose of this sample, we're not using
// named routes. Because of that, the onGenerateRoute callback
// will be called only for the initial route.
onGenerateRoute: (settings) => MaterialPageRoute(
settings: settings,
builder: item.initialPageBuilder,
: Container(),
if (tabIndex == widget.selectedIndex) {
return view;
} else {
if (_animationControllers[tabIndex].isAnimating) {
return IgnorePointer(child: view);
return Offstage(child: view);
/// Extension class of BottomNavigationTab that adds another GlobalKey to it
/// in order to use it within the KeyedSubtree widget.
class _MaterialBottomNavigationTab extends BottomNavigationTab {
const _MaterialBottomNavigationTab({
@required BottomNavigationBarItem bottomNavigationBarItem,
@required GlobalKey<NavigatorState> navigatorKey,
@required WidgetBuilder initialPageBuilder,
@required this.subtreeKey,
}) : assert(bottomNavigationBarItem != null),
assert(subtreeKey != null),
assert(initialPageBuilder != null),
assert(navigatorKey != null),
bottomNavigationBarItem: bottomNavigationBarItem,
navigatorKey: navigatorKey,
initialPageBuilder: initialPageBuilder,
final GlobalKey subtreeKey;
You can’t perform that action at this time.