🛣️

GoRouterで学ぶ画面遷移の基本:Bottom Navigation Bar編

に公開

はじめに

Flutterアプリケーション開発において、画面遷移の管理は重要な要素の一つです。この記事では、go_routerパッケージを使用した画面遷移の実装方法、特にBottom Navigation Barでの画面切り替えについて解説します。

builderとpageBuilderの違い

builder

  • 基本的な画面遷移に使用
  • シンプルな実装が可能
  • アニメーションのカスタマイズは限定的
  • 主にShellRouteで使用
GoRoute(
  path: '/home',
  builder: (context, state) => HomeScreen(),
),

pageBuilder

  • 詳細な画面遷移の制御が可能
  • カスタムトランジションの実装に適している
  • Bottom Navigation Bar での画面切り替えに最適
  • NoTransitionPageとの相性が良い
GoRoute(
  path: '/profile',
  pageBuilder: (context, state) => NoTransitionPage(
    child: ProfileScreen(),
  ),
),

Bottom Navigation Barでの画面遷移

基本的な実装

// routes.dart
class AppRouter {
  static final _shellNavigatorKey = GlobalKey<NavigatorState>();
  
  static final router = GoRouter(
    routes: [
      ShellRoute(
        navigatorKey: _shellNavigatorKey,
        builder: (context, state, child) {
          return ScaffoldWithNavBar(child: child);
        },
        routes: [
          GoRoute(
            path: '/home',
            pageBuilder: (context, state) => NoTransitionPage(
              child: HomeScreen(),
            ),
          ),
          GoRoute(
            path: '/search',
            pageBuilder: (context, state) => NoTransitionPage(
              child: SearchScreen(),
            ),
          ),
          GoRoute(
            path: '/profile',
            pageBuilder: (context, state) => NoTransitionPage(
              child: ProfileScreen(),
            ),
          ),
        ],
      ),
    ],
  );
}

ScaffoldWithNavBarの実装例

class ScaffoldWithNavBar extends StatelessWidget {
  final Widget child;

  const ScaffoldWithNavBar({super.key, required this.child});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _calculateSelectedIndex(context),
        onTap: (index) => _onItemTapped(index, context),
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'ホーム',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: '検索',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'プロフィール',
          ),
        ],
      ),
    );
  }

  int _calculateSelectedIndex(BuildContext context) {
    final String location = GoRouterState.of(context).location;
    if (location.startsWith('/home')) return 0;
    if (location.startsWith('/search')) return 1;
    if (location.startsWith('/profile')) return 2;
    return 0;
  }

  void _onItemTapped(int index, BuildContext context) {
    switch (index) {
      case 0:
        context.go('/home');
        break;
      case 1:
        context.go('/search');
        break;
      case 2:
        context.go('/profile');
        break;
    }
  }
}

NoTransitionPageを使用する理由

  1. UXの向上

    • Bottom Navigation Barでの画面切り替えでは、スライドアニメーションは不自然
    • 即時的な切り替えがユーザー体験として適切
  2. パフォーマンスの最適化

    • 不要なアニメーションを省くことでパフォーマンスが向上
    • メモリ使用量の削減
  3. プラットフォーム間の一貫性

    • iOS/Androidで同じ動作を実現
    • プラットフォーム固有のアニメーションを回避

実践的なTips

  1. 状態の保持
// IndexedStackを使用して画面状態を保持
class ScaffoldWithNavBar extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _calculateSelectedIndex(context),
        children: const [
          HomeScreen(),
          SearchScreen(),
          ProfileScreen(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(...),
    );
  }
}
  1. ディープリンク対応
GoRoute(
  path: '/profile/:id',
  pageBuilder: (context, state) {
    final id = state.pathParameters['id']!;
    return NoTransitionPage(
      child: ProfileScreen(userId: id),
    );
  },
),
  1. 遷移アニメーションのカスタマイズ
// 必要に応じてカスタムトランジションを実装
CustomTransitionPage(
  key: state.pageKey,
  child: ProfileScreen(),
  transitionsBuilder: (context, animation, secondaryAnimation, child) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  },
),

まとめ

  • go_routerを使用することで、宣言的なルーティングが可能
  • Bottom Navigation Barでの画面切り替えにはNoTransitionPageが最適
  • 状態の保持やディープリンク対応も容易に実装可能
  • パフォーマンスとUXの両方を考慮した実装が重要

参考資料

Discussion