13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Flutter] go_routerでbottom navigationの出し入れをしてみる

Posted at

概要

Flutterでgo_routerパッケージを用いた時、bottomNavigationBarの扱いが少し面倒になるそうです。
簡易的ではあるものの、bottomNavigationを残しながら画面遷移し、時には消すような処理を実装していきます。

通常遷移してしまうと当然のように別ページが表示されてしまいますが、go_routerのドキュメントにもある通り、
「navigatorBuilder」を用いれば実現可能です。

完成イメージ

実装

フォルダ構成

Github

構成

lib
 ∟provider <- BottomNavigationの切り替えと、表示非表示
 ∟screens <- BottomNavigationの土台の画面
 ∟widgets <- BottomNavigationの土台(表示用・非表示用)
 ∟main.dart <- GoRouterの初期設定
 ∟route.dart <- ルーティング設定(BNBの表示切り替えも)

主要部分

lib/route.dart
final routerProvider = Provider((ref) {
  return GoRouter(
    initialLocation: '/bottomA',
    routes: <GoRoute>[
      /// BottomNavigationのタブのトップページ
      GoRoute(
        path: '/bottomA',
        pageBuilder: (context, state) => CustomTransitionPage(
            key: state.pageKey,
            child: const BottomNavigationTopA(),
            transitionDuration: Duration.zero,
            transitionsBuilder:
                (context, animation, secondaryAnimation, child) => child),
        /// 同じタブ内で遷移したい画面リストをまとめる
        routes: [
          GoRoute(
            path: 'pageA',
            pageBuilder: (context, state) => MaterialPage(
              key: state.pageKey,
              child: const BottomAPageA(),
            ),
          ),
          GoRoute(
            path: 'pageB',
            pageBuilder: (context, state) => MaterialPage(
              key: state.pageKey,
              child: const BottomAPageB(),
            ),
          ),
          GoRoute(
            path: 'pageC',
            pageBuilder: (context, state) => MaterialPage(
              key: state.pageKey,
              child: const BottomAPageC(),
            ),
      // ...タブの数分上記を書く...
    ],
    /// 常時表示したいウィジェットを定義できる
    navigatorBuilder: (context, state, child) {
      /// state.locationで現在のパスが取れる。
      /// パスがProviderにある非表示リストに合致していれば非表示にする
      if (ref
          .read(hideBottomNavigationProvider)
          .isHideBottomNavigation(state.location)) {
        return Navigator(
          onPopPage: (route, dynamic __) => false,
          pages: [
            MaterialPage<Widget>(
              /// BottomNavigationがない土台
              child: EmptyBottomNavigation(
                child: child,
              ),
            ),
          ],
        );
      } else {
        return Navigator(
          onPopPage: (route, dynamic __) => false,
          pages: [
            MaterialPage<Widget>(
              /// BottomNavigationがある土台
              child: MyBottomNavigation(
                child: child,
              ),
            ),
          ],
        );
      }
    },
  );
});

Provider部分

lib/provider/bottom_navigation_provider.dart
// BNBを切り替えるだけ
final bottomNavIndexProvider = StateProvider((ref) => 0);
lib/provider/hide_bottom_navigation_provider.dart
final hideBottomNavigationProvider =
    ChangeNotifierProvider<HideBottomNavigation>(
  (ref) {
    return HideBottomNavigation();
  },
);

class HideBottomNavigation extends ChangeNotifier {
  HideBottomNavigation();
  /// 非表示リスト(別ファイルに定義した方が好ましい)
  static const List<String> hideBottomNavigationPageList = [
    '/bottomC/pageA',
    '/bottomC/pageB',
    '/bottomC/pageC',
  ];

  /// [hideBottomNavigationPageList]に含まれる場合BNBを非表示
  bool isHideBottomNavigation(String currentPath) {
    return hideBottomNavigationPageList.contains(currentPath);
  }
}

BottomNavigationの表示

lib/widgets/bottom_navigation.dart
class MyBottomNavigation extends ConsumerStatefulWidget {
  const MyBottomNavigation({
    super.key,
    required this.child,
  });

  final Widget child;

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _MyBottomNavigationState();
}

class _MyBottomNavigationState extends ConsumerState<MyBottomNavigation> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          widget.child, // routerで選択された画面をそのまま表示
          /// シンプルにBottomNavigationを最下部に配置
          Align(
            alignment: Alignment.bottomCenter,
            child: BottomNavigationBar(
              type: BottomNavigationBarType.fixed,
              currentIndex: ref.watch(bottomNavIndexProvider),
              onTap: (i) {
                ref.read(bottomNavIndexProvider.notifier).update((state) => i);

                // indexに応じてGoRouterのページに遷移
                switch (i) {
                  case 0:
                    context.go('/bottomA');
                    break;
                  case 1:
                    context.go('/bottomB');
                    break;
                  case 2:
                    context.go('/bottomC');
                    break;
                }
              },
              items: const [
                BottomNavigationBarItem(
                  icon: Icon(Icons.arrow_forward, color: Colors.grey),
                  label: 'BottomA',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.replay, color: Colors.grey),
                  label: 'BottomB',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.tab, color: Colors.grey),
                  label: 'BottomC',
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

BottomNavigationの非表示

lib/widgets/empty_bottom_navigation.dart
class EmptyBottomNavigation extends ConsumerStatefulWidget {
  const EmptyBottomNavigation({
    super.key,
    required this.child,
  });

  final Widget child;

  @override
  ConsumerState<ConsumerStatefulWidget> createState() =>
      _EmptyBottomNavigationState();
}

class _EmptyBottomNavigationState extends ConsumerState<EmptyBottomNavigation> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: widget.child,
    );
  }
}

以上です!
まだ深く踏み込んだわけではないのでこれがどんな弊害があるか理解しきれていませんが、
複雑なUIでなければ実装可能かと思います!

13
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
13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?