如何在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
相关产品推荐
相关产品推荐

