· 在 ShellRoute 内部:自动有底部导航
· 在 ShellRoute 外部:自动没有底部导航
🎯 方案特点
· ✅ 简洁明了:最小化代码,最大化效果
· ✅ 开箱即用:复制即运行
· ✅ 易于扩展:添加页面只需1分钟
· ✅ 状态管理:Riverpod 提供响应式状态
📁 项目结构
lib/
├── main.dart # 应用入口
├── app.dart # 应用配置
├── router.dart # 路由配置(核心文件)
├── bottom_nav.dart # 底部导航组件
└── pages/ # 页面目录
├── home_page.dart
├── category_page.dart
├── login_page.dart
└── product_page.dart
- 核心文件:router.dart
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'pages/home_page.dart';
import 'pages/category_page.dart';
import 'pages/login_page.dart';
import 'pages/product_page.dart';
import 'bottom_nav.dart';
// 当前标签页索引
final tabIndexProvider = StateProvider<int>((ref) => 0);
// 路由配置
final routerProvider = Provider<GoRouter>((ref) {
return GoRouter(
initialLocation: '/',
routes: [
// ShellRoute:底部导航页面组
ShellRoute(
builder: (context, state, child) {
final index = ref.watch(tabIndexProvider);
return Scaffold(
body: child,
bottomNavigationBar: BottomNav(
currentIndex: index,
onTabSelected: (i) {
ref.read(tabIndexProvider.notifier).state = i;
_goToTab(i, context);
},
),
);
},
routes: _buildTabRoutes(),
),
// 独立页面:无底部导航
..._buildIndependentRoutes(),
],
);
});
// 标签页路由
List<GoRoute> _buildTabRoutes() => [
GoRoute(path: '/', builder: (_, __) => const HomePage()),
GoRoute(path: '/category', builder: (_, __) => const CategoryPage()),
];
// 独立页面路由
List<GoRoute> _buildIndependentRoutes() => [
GoRoute(
path: '/login',
builder: (_, __) => const LoginPage(),
),
GoRoute(
path: '/product/:id',
builder: (_, state) {
final id = state.pathParameters['id']!;
return ProductPage(id: id);
},
),
];
// 标签页跳转
void _goToTab(int index, BuildContext context) {
switch (index) {
case 0: context.go('/'); break;
case 1: context.go('/category'); break;
}
}
- 底部导航组件:bottom_nav.dart
dart
import 'package:flutter/material.dart';
class BottomNav extends StatelessWidget {
final int currentIndex;
final Function(int) onTabSelected;
const BottomNav({
super.key,
required this.currentIndex,
required this.onTabSelected,
});
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: currentIndex,
onTap: onTabSelected,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label: '分类',
),
],
);
}
}
- 应用配置:app.dart
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'router.dart';
class App extends ConsumerWidget {
const App({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
routerConfig: router,
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
);
}
}
- 应用入口:main.dart
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'app.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const App();
}
}
- 页面示例
首页(有底部导航)
dart
// pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('这是有底部导航的页面'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => context.go('/product/123'),
child: const Text('查看商品'),
),
ElevatedButton(
onPressed: () => context.go('/login'),
child: const Text('去登录'),
),
],
),
),
);
}
}
分类页(有底部导航)
dart
// pages/category_page.dart
import 'package:flutter/material.dart';
class CategoryPage extends StatelessWidget {
const CategoryPage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
appBar: AppBar(title: Text('分类')),
body: Center(child: Text('分类页面')),
);
}
}
登录页(无底部导航)
dart
// pages/login_page.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('登录'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
if (context.canPop()) context.pop();
else context.go('/');
},
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('这是没有底部导航的页面'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('登录成功,返回首页'),
),
],
),
),
);
}
}
商品页(无底部导航)
dart
// pages/product_page.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class ProductPage extends StatelessWidget {
final String id;
const ProductPage({super.key, required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('商品 $id'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop(),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('商品ID: $id'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('返回首页'),
),
],
),
),
);
}
}
🚀 快速开始
步骤 1:创建项目
bash
flutter create myapp
cd myapp
步骤 2:添加依赖
bash
flutter pub add go_router flutter_riverpod
步骤 3:替换文件
将上面的代码复制到对应的文件中。
步骤 4:运行项目
bash
flutter run
🔧 扩展说明
添加新的底部导航页面
dart
// 1. 在 _buildTabRoutes() 中添加路由
GoRoute(path: '/cart', builder: (_, __) => const CartPage()),
// 2. 在 BottomNav 中添加图标
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
label: '购物车',
),
// 3. 在 _goToTab() 中添加跳转逻辑
case 2: context.go('/cart'); break;
添加新的独立页面
dart
// 直接在 _buildIndependentRoutes() 中添加
GoRoute(
path: '/settings',
builder: (_, __) => const SettingsPage(),
),
💡 核心原理
ShellRoute 是关键
· 在 ShellRoute 内部:自动有底部导航
· 在 ShellRoute 外部:自动没有底部导航
状态管理
· tabIndexProvider:管理当前选中的标签页
· routerProvider:提供路由配置
跳转逻辑
· 点击底部导航:更新状态 + 路由跳转
· 页面间跳转:直接使用 context.go()
🎯 总结
这个方案的核心优势:
- 极简:最少的代码实现完整功能
- 清晰:路由分组一目了然
- 易用:添加页面只需1-3行代码
- 高效:利用框架特性,避免过度设计