4.4 动态与嵌套路由

动态路由处理 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)

👉 下一章:五、网络与数据

相关推荐
2401_839633912 小时前
鸿蒙flutter第三方库适配 - 存储空间分析
flutter·华为·harmonyos
加农炮手Jinx3 小时前
Flutter 三方库 better_commit 的鸿蒙化适配指南 - 实现具备语义化提交规范与自动化交互的 Git 工作流插件、支持端侧版本工程的高效规范化审计实战
flutter·harmonyos·鸿蒙·openharmony·better_commit
空中海3 小时前
3.3 第三方框架
flutter·dart
麒麟ZHAO3 小时前
鸿蒙flutter第三方库适配 - 文件搜索工具
flutter·华为·harmonyos
2401_839633913 小时前
鸿蒙flutter第三方库适配 - 二维表格
flutter·华为·harmonyos
麒麟ZHAO3 小时前
鸿蒙flutter第三方库适配 - Google登录演示
flutter·华为·harmonyos
2401_839633913 小时前
鸿蒙flutter第三方库适配 - 日历网格
flutter·华为·harmonyos
2401_839633913 小时前
鸿蒙flutter第三方库适配 - 数据网格
flutter·华为·harmonyos
见山是山-见水是水3 小时前
鸿蒙flutter第三方库适配 - 汇率换算器
redis·flutter·华为·harmonyos