🛣️
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を使用する理由
-
UXの向上
- Bottom Navigation Barでの画面切り替えでは、スライドアニメーションは不自然
- 即時的な切り替えがユーザー体験として適切
-
パフォーマンスの最適化
- 不要なアニメーションを省くことでパフォーマンスが向上
- メモリ使用量の削減
-
プラットフォーム間の一貫性
- iOS/Androidで同じ動作を実現
- プラットフォーム固有のアニメーションを回避
実践的なTips
- 状態の保持
// IndexedStackを使用して画面状態を保持
class ScaffoldWithNavBar extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _calculateSelectedIndex(context),
children: const [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
],
),
bottomNavigationBar: BottomNavigationBar(...),
);
}
}
- ディープリンク対応
GoRoute(
path: '/profile/:id',
pageBuilder: (context, state) {
final id = state.pathParameters['id']!;
return NoTransitionPage(
child: ProfileScreen(userId: id),
);
},
),
- 遷移アニメーションのカスタマイズ
// 必要に応じてカスタムトランジションを実装
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