动态路由处理 Tab/BottomNavigationBar 等多层导航场景,深链(Deep Linking)则打通 App 与外部系统的连接。
一、Tab / BottomNavigationBar 场景
1.1 保持各 Tab 状态(IndexedStack)
dart
class MainPage extends StatefulWidget {
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
// 各 Tab 的 Navigator Key(独立路由栈)
final List<GlobalKey<NavigatorState>> _navigatorKeys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack( // 保留所有 Tab 状态
index: _currentIndex,
children: [
_TabNavigator(navigatorKey: _navigatorKeys[0], initialRoute: '/home'),
_TabNavigator(navigatorKey: _navigatorKeys[1], initialRoute: '/explore'),
_TabNavigator(navigatorKey: _navigatorKeys[2], initialRoute: '/profile'),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
if (index == _currentIndex) {
// 双击 Tab:回到该 Tab 根页面
_navigatorKeys[index].currentState?.popUntil((r) => r.isFirst);
} else {
setState(() => _currentIndex = index);
}
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.explore), label: '发现'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
),
);
}
// 处理 Android 物理返回键
Future<bool> _onWillPop() async {
final canPop = _navigatorKeys[_currentIndex].currentState?.canPop() ?? false;
if (canPop) {
_navigatorKeys[_currentIndex].currentState?.pop();
return false;
}
return true; // 允许退出 App
}
}
class _TabNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final String initialRoute;
const _TabNavigator({required this.navigatorKey, required this.initialRoute});
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: initialRoute,
onGenerateRoute: AppRouter.generateRoute,
);
}
}
1.2 使用 GoRouter 的 StatefulShellRoute
dart
final router = GoRouter(
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return MainScaffold(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/home',
builder: (_, __) => const HomePage(),
routes: [
GoRoute(
path: 'article/:id',
builder: (_, state) => ArticlePage(
id: state.pathParameters['id']!,
),
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(path: '/explore', builder: (_, __) => const ExplorePage()),
],
),
StatefulShellBranch(
initialLocation: '/profile',
routes: [
GoRoute(path: '/profile', builder: (_, __) => const ProfilePage()),
],
),
],
),
],
);
class MainScaffold extends StatelessWidget {
final StatefulNavigationShell navigationShell;
const MainScaffold({super.key, required this.navigationShell});
@override
Widget build(BuildContext context) {
return Scaffold(
body: navigationShell, // 自动管理各 Branch 状态
bottomNavigationBar: NavigationBar(
selectedIndex: navigationShell.currentIndex,
onDestinationSelected: (index) {
navigationShell.goBranch(
index,
// 双击 Tab 回到根
initialLocation: index == navigationShell.currentIndex,
);
},
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: '首页'),
NavigationDestination(icon: Icon(Icons.explore), label: '发现'),
NavigationDestination(icon: Icon(Icons.person), label: '我的'),
],
),
);
}
}
二、WebView 嵌套场景
dart
// 在 Tab 内部推入 WebView(不影响底部导航)
GoRoute(
path: '/home',
builder: (_, __) => const HomePage(),
routes: [
GoRoute(
path: 'webview',
builder: (_, state) {
final url = Uri.decodeComponent(state.uri.queryParameters['url']!);
return AppWebViewPage(url: url);
},
),
],
)
// 打开 WebView
context.push('/home/webview?url=${Uri.encodeComponent('https://example.com')}');
三、深链(Deep Linking)
3.1 配置 Android 深链
android/app/src/main/AndroidManifest.xml:
xml
<activity>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 自定义 Scheme:myapp://product/42 -->
<data android:scheme="myapp" android:host="product" />
<!-- HTTPS 深链:https://example.com/product/42 -->
<data android:scheme="https" android:host="example.com" />
</intent-filter>
</activity>
3.2 配置 iOS 深链
ios/Runner/Info.plist(URL Schemes):
xml
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Universal Links(ios/Runner/Runner.entitlements):
xml
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
</array>
3.3 uni_links(监听深链)
yaml
dependencies:
uni_links: ^0.5.1
dart
class DeepLinkHandler extends StatefulWidget {
final Widget child;
const DeepLinkHandler({super.key, required this.child});
@override
State<DeepLinkHandler> createState() => _DeepLinkHandlerState();
}
class _DeepLinkHandlerState extends State<DeepLinkHandler> {
StreamSubscription? _linkSub;
@override
void initState() {
super.initState();
_initDeepLink();
}
Future<void> _initDeepLink() async {
// 处理冷启动时的深链(App 未在运行)
try {
final initialUri = await getInitialUri();
if (initialUri != null) _handleDeepLink(initialUri);
} catch (e) {
debugPrint('Deep link error: $e');
}
// 处理热启动时的深链(App 在后台)
_linkSub = uriLinkStream.listen(
(uri) { if (uri != null) _handleDeepLink(uri); },
onError: (e) => debugPrint('Deep link stream error: $e'),
);
}
void _handleDeepLink(Uri uri) {
// myapp://product/42
// https://example.com/product/42
switch (uri.pathSegments.first) {
case 'product':
final id = uri.pathSegments.last;
router.push('/product/$id');
break;
case 'category':
router.push('/category/${uri.pathSegments.last}');
break;
case 'share':
final code = uri.queryParameters['code'];
router.push('/share?code=$code');
break;
}
}
@override
void dispose() {
_linkSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}
3.4 GoRouter 原生深链支持
GoRouter 对深链有原生支持,只需配置路由即可:
dart
final router = GoRouter(
// GoRouter 自动解析 Deep Link
routes: [
GoRoute(path: '/product/:id', builder: ...),
],
);
// Flutter 配置(pubspec.yaml)
// flutter:
// deeplinking:
// enabled: true
小结
| 场景 | 推荐方案 |
|---|---|
| Tab 保持状态 | IndexedStack + 独立 Navigator |
| GoRouter Tab | StatefulShellRoute.indexedStack |
| 深链(Schema) | uni_links + 手动解析 |
| 深链(URL) | GoRouter 原生支持 |
| 双击 Tab 回根 | popUntil(isFirst) 或 goBranch(initialLocation: true) |
👉 下一章:五、网络与数据