You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

go_router中StatefulShellRoute搭配CustomTransitionPage无效问题

解决GoRouter StatefulShellRoute Tab切换时CustomTransitionPage不生效的问题

你遇到的问题根源在于StatefulShellRoute.indexedStack的底层实现是用IndexedStack管理各个分支的Navigator,切换Tab时只是改变IndexedStack的显示索引,并不会触发路由页面的push/pop操作,所以你在GoRoute或StatefulShellRoute的pageBuilder中定义的CustomTransitionPage不会被触发——因为根本没有发生路由页面的跳转动作。

要实现Tab切换时的过渡动画,你不需要依赖CustomTransitionPage,而是直接在承载navigationShell的父组件中添加动画逻辑,比如使用AnimatedSwitcher包裹navigationShell,在切换Tab时触发过渡效果。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final _sectionANavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'sectionANav');

void main() {
  runApp(NestedTabNavigationExampleApp());
}

class NestedTabNavigationExampleApp extends StatelessWidget {
  NestedTabNavigationExampleApp({super.key});

  final GoRouter _router = GoRouter(
    navigatorKey: _rootNavigatorKey,
    initialLocation: '/a',
    routes: <RouteBase>[
      StatefulShellRoute.indexedStack(
        // 恢复为builder,shell过渡交给AnimatedSwitcher处理
        builder: (context, state, navigationShell) {
          return ScaffoldWithNavBar(navigationShell: navigationShell);
        },
        branches: <StatefulShellBranch>[
          StatefulShellBranch(
            navigatorKey: _sectionANavigatorKey,
            routes: <RouteBase>[
              GoRoute(
                path: '/a',
                builder: (context, state) => const RootScreen(label: 'A'),
              ),
            ],
          ),
          StatefulShellBranch(
            routes: <RouteBase>[
              GoRoute(
                path: '/b',
                builder: (context, state) => const RootScreen(label: 'B'),
              ),
            ],
          ),
          StatefulShellBranch(
            routes: <RouteBase>[
              GoRoute(
                path: '/c',
                builder: (context, state) => const RootScreen(label: 'C'),
              ),
            ],
          ),
        ],
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      routerConfig: _router,
    );
  }
}

class ScaffoldWithNavBar extends StatelessWidget {
  const ScaffoldWithNavBar({
    required this.navigationShell,
    Key? key,
  }) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));

  final StatefulNavigationShell navigationShell;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 用AnimatedSwitcher实现Tab切换动画
      body: AnimatedSwitcher(
        duration: const Duration(milliseconds: 300),
        transitionBuilder: (child, animation) {
          // 自定义过渡效果,可替换为平移、缩放等动画
          return FadeTransition(
            opacity: animation,
            child: child,
          );
        },
        // 给每个Tab的shell设置唯一key,确保AnimatedSwitcher识别组件变化
        child: KeyedSubtree(
          key: ValueKey(navigationShell.currentIndex),
          child: navigationShell,
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
          BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
          BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Section C'),
        ],
        currentIndex: navigationShell.currentIndex,
        onTap: (int index) => _onTap(context, index),
      ),
    );
  }

  void _onTap(BuildContext context, int index) {
    navigationShell.goBranch(
      index,
      initialLocation: index == navigationShell.currentIndex,
    );
  }
}

class RootScreen extends StatelessWidget {
  const RootScreen({
    required this.label,
    super.key,
  });

  final String label;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Root of section $label')),
      body: Center(child: Text('Screen $label')),
    );
  }
}

关键修改说明

  1. 移除冗余的CustomTransitionPage:Tab切换不属于路由页面跳转,因此GoRoute和StatefulShellRoute无需使用pageBuilder,改回普通的builder即可。
  2. 添加AnimatedSwitcher:通过KeyedSubtree给每个Tab对应的shell设置唯一key,确保切换时AnimatedSwitcher能检测到组件变化并触发动画。
  3. 自定义过渡效果:在AnimatedSwitcher的transitionBuilder中可以实现任意过渡动画,比如淡入淡出、滑动、缩放等,替换示例中的FadeTransition即可。

如果需要更复杂的路由级过渡,你需要自定义StatefulShellRoute的shell实现,放弃indexedStack,手动管理分支Navigator的切换逻辑,但这种场景相对少见,上述方案已能满足绝大多数Tab切换动画需求。

内容的提问来源于stack exchange,提问作者Ahmed Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 10:05:15