環境+ライブラリ
pubspec.yaml
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
state_notifier:
flutter_state_notifier:
freezed_annotation:
flutter_riverpod:
dev_dependencies:
flutter_test:
sdk: flutter
json_serializable:
build_runner:
freezed:
実装
tab_item.dart
enum TabItem { home, list, setting, camera }
const Map<TabItem, String> tabName = {
TabItem.home: 'home',
TabItem.list: 'list',
TabItem.setting: 'setting',
TabItem.camera: 'camera',
};
const Map<TabItem, IconData> tabIcon = {
TabItem.home: Icons.home,
TabItem.list: Icons.list,
TabItem.setting: Icons.settings,
TabItem.camera: Icons.camera,
};
bottom_navigation.dart
class BottomNavigation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _BottomNavigationWidget();
}
}
class _BottomNavigationWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
final state = watch(navigationProvider);
final provider = watch(navigationProvider.notifier);
return WillPopScope(
onWillPop: provider.onWillPop,
child: Scaffold(
body: Stack(children: _stackList(state, provider)),
bottomNavigationBar: CustomBottomNavigationBar(
currentTab: state.currentTab,
onSelectTab: provider.selectTab,
),
),
);
}
List<Widget> _stackList(
NavigationState state, NavigationStateNotifier notifier) {
List<Widget> result = [];
notifier.navigatorKeys.forEach((key, value) {
result.add(Offstage(
offstage: state.currentTab != key,
child: TabNavigator(
navigatorKey: value,
tabItem: key,
),
));
});
return result;
}
}
custom_bottom_navigation_bar.dart
class CustomBottomNavigationBar extends StatelessWidget {
CustomBottomNavigationBar(
{required this.currentTab, required this.onSelectTab});
final TabItem currentTab;
final ValueChanged<TabItem> onSelectTab;
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
_buildItem(TabItem.home),
_buildItem(TabItem.list),
_buildItem(TabItem.setting),
_buildItem(TabItem.camera),
],
onTap: (index) => onSelectTab(TabItem.values[index]),
);
}
BottomNavigationBarItem _buildItem(TabItem tabItem) {
return BottomNavigationBarItem(
icon: Icon(
_tabIcon(tabItem),
color: _tabColor(tabItem),
),
label: '',
);
}
Color _tabColor(TabItem item) {
return currentTab == item ? Colors.blue : Colors.grey;
}
IconData _tabIcon(TabItem item) {
return tabIcon[item]!;
}
}
tab_navigator.dart
class TabNavigator extends StatelessWidget {
TabNavigator({required this.navigatorKey, required this.tabItem});
final GlobalKey<NavigatorState> navigatorKey;
final TabItem tabItem;
final String root = '/';
Map<String, WidgetBuilder> _routeBuilders(BuildContext context) {
Map<String, WidgetBuilder> value;
switch (tabItem) {
case TabItem.home:
value = {root: (context) => HomePage()};
break;
case TabItem.list:
value = {root: (context) => ListPage()};
break;
case TabItem.setting:
value = {root: (context) => SettingPage()};
break;
case TabItem.camera:
value = {root: (context) => CameraPage()};
break;
}
return value;
}
@override
Widget build(BuildContext context) {
final routeBuilders = _routeBuilders(context);
return Navigator(
key: navigatorKey,
initialRoute: root,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => routeBuilders[routeSettings.name]!(context),
);
},
);
}
}
navigation_state.dart
part 'navigation_state.freezed.dart';
final navigationProvider =
StateNotifierProvider((_) => NavigationStateNotifier());
@freezed
abstract class NavigationState with _$NavigationState {
const factory NavigationState({
@Default(TabItem.home) TabItem currentTab,
}) = _NavigationState;
}
class NavigationStateNotifier extends StateNotifier<NavigationState> {
NavigationStateNotifier() : super(const NavigationState());
final navigatorKeys = {
TabItem.home: GlobalKey<NavigatorState>(),
TabItem.list: GlobalKey<NavigatorState>(),
TabItem.setting: GlobalKey<NavigatorState>(),
TabItem.camera: GlobalKey<NavigatorState>(),
};
Future<bool> onWillPop() async {
var _currentTab = state.currentTab;
final isFirstRouteInCurrentTab =
!await navigatorKeys[_currentTab]!.currentState!.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentTab != TabItem.home) {
selectTab(TabItem.home);
return false;
}
}
return isFirstRouteInCurrentTab;
}
void selectTab(TabItem tabItem) {
if (tabItem == state.currentTab) {
navigatorKeys[tabItem]!.currentState!.popUntil((route) => route.isFirst);
} else {
state = state.copyWith(currentTab: tabItem);
}
}
}
注意
各種タブの画面でのダイアログ表示ではrootNavigatorをtrueで設定する