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

Flutter Web:结合GoRouter与PageView Builder实现命名路由导航并复用Scaffold

在Flutter Web中结合GoRouter与PageView实现单Scaffold下的命名路由导航

要实现这个需求,核心是利用GoRouter的ShellRoute承载全局Scaffold,同时将PageView作为Shell的内容容器,让命名路由与PageView的页面项一一绑定,实现路由切换仅更新PageView内容,而Scaffold的全局UI保持不变。下面是完整的实现方案:

1. 依赖准备

确保你的pubspec.yaml中添加了go_router依赖:

dependencies:
  flutter:
    sdk: flutter
  go_router: ^12.0.0

2. 定义页面组件

创建示例页面,用StatefulWidget+PageStorage机制保持页面状态,避免切换时重初始化:

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('首页', style: Theme.of(context).textTheme.headlineLarge),
    );
  }
}

class ProfilePage extends StatefulWidget {
  const ProfilePage({super.key});

  @override
  State<ProfilePage> createState() => _ProfilePageState();
}

class _ProfilePageState extends State<ProfilePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('个人中心 - 计数: $_counter', style: Theme.of(context).textTheme.headlineMedium),
          ElevatedButton(
            onPressed: () => setState(() => _counter++),
            child: const Text('增加计数'),
          ),
        ],
      ),
    );
  }
}

class SettingsPage extends StatelessWidget {
  const SettingsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('设置页', style: Theme.of(context).textTheme.headlineLarge),
    );
  }
}

3. 配置GoRouter与ShellRoute

用ShellRoute作为根路由承载全局Scaffold,子路由对应PageView的各个页面:

final _router = GoRouter(
  initialLocation: '/home',
  routes: [
    ShellRoute(
      builder: (context, state, child) => ShellWidget(state: state),
      routes: [
        GoRoute(
          path: '/home',
          builder: (context, state) => const HomePage(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => const ProfilePage(),
        ),
        GoRoute(
          path: '/settings',
          builder: (context, state) => const SettingsPage(),
        ),
      ],
    ),
  ],
);

4. 实现ShellWidget(单Scaffold+PageView)

这个Widget是全局Scaffold的载体,负责路由与PageView的双向同步:

class ShellWidget extends StatefulWidget {
  final GoRouterState state;
  const ShellWidget({super.key, required this.state});

  @override
  State<ShellWidget> createState() => _ShellWidgetState();
}

class _ShellWidgetState extends State<ShellWidget> {
  late final PageController _pageController;
  int _currentIndex = 0;

  // 路由路径与PageView索引的映射
  final Map<String, int> _routeIndexMap = {
    '/home': 0,
    '/profile': 1,
    '/settings': 2,
  };

  // 索引对应的路由路径
  final List<String> _indexRouteMap = [
    '/home',
    '/profile',
    '/settings',
  ];

  @override
  void initState() {
    super.initState();
    _currentIndex = _routeIndexMap[widget.state.location] ?? 0;
    _pageController = PageController(initialPage: _currentIndex);
    // 监听路由变化,更新PageView索引
    GoRouter.of(context).addListener(_onRouteChanged);
  }

  @override
  void dispose() {
    GoRouter.of(context).removeListener(_onRouteChanged);
    _pageController.dispose();
    super.dispose();
  }

  void _onRouteChanged() {
    final location = GoRouter.of(context).location;
    final index = _routeIndexMap[location] ?? 0;
    if (_currentIndex != index) {
      setState(() => _currentIndex = index);
      _pageController.jumpToPage(index);
    }
  }

  void _onPageChanged(int index) {
    if (_currentIndex != index) {
      setState(() => _currentIndex = index);
      // 切换路由,保证URL与页面同步
      GoRouter.of(context).go(_indexRouteMap[index]);
    }
  }

  Widget _buildPage(int index) {
    switch (index) {
      case 0:
        return const HomePage();
      case 1:
        return const ProfilePage();
      case 2:
        return const SettingsPage();
      default:
        return const SizedBox.shrink();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('全局导航栏')),
      body: PageView.builder(
        controller: _pageController,
        // 不需要滑动切换可设置为NeverScrollableScrollPhysics,否则移除
        physics: const NeverScrollableScrollPhysics(),
        onPageChanged: _onPageChanged,
        itemCount: _indexRouteMap.length,
        itemBuilder: (context, index) => _buildPage(index),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: _onPageChanged,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '个人中心'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
        ],
      ),
    );
  }
}

5. 初始化应用

用MaterialApp.router集成GoRouter:

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

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

关键说明

  • ShellRoute的作用:作为根路由承载全局Scaffold,确保Scaffold只渲染一次,子路由内容通过PageView切换,不会触发Scaffold重建。
  • 双向同步:监听GoRouter路由变化更新PageView索引,同时PageView切换时更新路由,保证URL与页面内容一致。
  • 状态保持:利用PageView默认的PageStorage机制,结合StatefulWidget,页面切换时不会丢失临时状态(比如ProfilePage的计数)。
  • Web适配:GoRouter自动处理浏览器前进后退的路由历史,无需额外配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 02:15:07