LoginSignup
0
0

More than 1 year has passed since last update.

【Flutter】BottomNavigationのタブキープ遷移

Last updated at Posted at 2021-05-13

環境+ライブラリ

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で設定する

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0