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

如何在GoRouter的ShellRoute中使用CustomScrollView?

问题根源

GoRouter的子路由builder返回的Widget会被自动包裹在Page组件(如MaterialPage)中,这类组件属于RenderBox类型,无法直接作为Sliver放入CustomScrollView的slivers列表;反之,若子路由直接返回Sliver组件,外层的Page组件又无法渲染Sliver类型,导致双向类型冲突。

解决方案

无需在每个路由中重复创建CustomScrollView和SliverAppBar,以下两种方案可实现复用需求:

方案一:使用NestedScrollView实现联动滚动(推荐,适合初学者)

利用NestedScrollView将复用的SliverAppBar放在头部,子路由返回常规可滚动组件(如ListView、CustomScrollView)作为主体,既保证SliverAppBar复用,又能实现滚动联动效果:

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

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

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

  @override
  Widget build(BuildContext context) {
    var title = 'Flutter Demo';
    return MaterialApp.router(
      title: title,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      routerConfig: GoRouter(
        navigatorKey: GlobalKey<NavigatorState>(),
        routes: [
          ShellRoute(
            navigatorKey: GlobalKey<NavigatorState>(),
            builder: (BuildContext context, GoRouterState state, Widget child) {
              return NestedScrollView(
                // 配置复用的SliverAppBar
                headerSliverBuilder: (context, innerBoxIsScrolled) => [
                  SliverAppBar(
                    expandedHeight: 300.0,
                    flexibleSpace: FlexibleSpaceBar(
                      background: Container(
                        color: Colors.red,
                        child: const Text("Shrinkable"),
                      ),
                    ),
                  ),
                ],
                // 子路由内容作为body,接受RenderBox类型组件
                body: child,
              );
            },
            routes: [
              GoRoute(
                path: "/",
                builder: (BuildContext context, GoRouterState state) {
                  // 返回常规可滚动组件,此处用CustomScrollView示例
                  return CustomScrollView(
                    slivers: [
                      SliverList(
                        delegate: SliverChildListDelegate(
                          List.generate(50, (index) => Padding(
                            padding: const EdgeInsets.all(16.0),
                            child: Text("Item $index"),
                          )),
                        ),
                      ),
                    ],
                  );
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

方案二:自定义SliverProvider传递Sliver组件

若需要子路由直接提供Sliver(而非完整可滚动组件),可通过自定义SliverProvider组件传递Sliver,在ShellRoute中提取并加入CustomScrollView的slivers列表:

1. 定义SliverProvider组件

class SliverProvider extends StatelessWidget {
  final Widget sliver;
  const SliverProvider({super.key, required this.sliver});

  @override
  Widget build(BuildContext context) {
    return const SizedBox.shrink(); // 仅作为Sliver的载体,不渲染实际内容
  }

  // 静态方法:从上下文获取传递的Sliver
  static Widget? of(BuildContext context) {
    return context.findAncestorWidgetOfExactType<SliverProvider>()?.sliver;
  }
}

2. 修改ShellRoute和子路由

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

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

class SliverProvider extends StatelessWidget {
  final Widget sliver;
  const SliverProvider({super.key, required this.sliver});

  @override
  Widget build(BuildContext context) {
    return const SizedBox.shrink();
  }

  static Widget? of(BuildContext context) {
    return context.findAncestorWidgetOfExactType<SliverProvider>()?.sliver;
  }
}

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

  @override
  Widget build(BuildContext context) {
    var title = 'Flutter Demo';
    return MaterialApp.router(
      title: title,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      routerConfig: GoRouter(
        navigatorKey: GlobalKey<NavigatorState>(),
        routes: [
          ShellRoute(
            navigatorKey: GlobalKey<NavigatorState>(),
            builder: (BuildContext context, GoRouterState state, Widget child) {
              return Builder(
                builder: (innerContext) {
                  // 从子路由获取传递的Sliver
                  final contentSliver = SliverProvider.of(innerContext);
                  return CustomScrollView(
                    slivers: [
                      SliverAppBar(
                        expandedHeight: 300.0,
                        flexibleSpace: FlexibleSpaceBar(
                          background: Container(
                            color: Colors.red,
                            child: const Text("Shrinkable"),
                          ),
                        ),
                      ),
                      if (contentSliver != null) contentSliver,
                    ],
                  );
                },
                child: child,
              );
            },
            routes: [
              GoRoute(
                path: "/",
                builder: (BuildContext context, GoRouterState state) {
                  // 返回SliverProvider,传递需要渲染的SliverList
                  return SliverProvider(
                    sliver: SliverList(
                      delegate: SliverChildListDelegate(
                        List.generate(50, (index) => Padding(
                          padding: const EdgeInsets.all(16.0),
                          child: Text("Item $index"),
                        )),
                      ),
                    ),
                  );
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者friend.code

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 17:02:01