使用GoRouter ShellRoute出现BoxConstraints无限高度错误的原因与解决
当使用go_router的ShellRoute共享BaseWidget时,出现了BoxConstraints forces an infinite height等渲染错误,但直接用BaseWidget包裹组件却完全正常。
问题原因
ShellRoute传入的child本质是一个Navigator组件,它默认会尝试占据父组件的全部可用空间(垂直方向无上限)。而你的BaseWidget使用ListView作为body,ListView的子组件在垂直方向没有固定高度约束(因为ListView自身是可滚动容器),这就导致Navigator无法确定自身的高度,触发了无限高度的布局冲突。
而直接使用BaseWidget的场景中,BodyWidget是一个Column,它的高度由自身子项的总高度决定,ListView可以正确计算它的尺寸,因此不会出现错误。
解决方案
方案一:调整布局结构(推荐)
修改BaseWidget的布局,让child拥有明确的高度约束,同时让内容自身处理滚动:
- 更新BaseWidget:
class BaseWidget extends StatelessWidget { final Widget child; const BaseWidget({super.key, required this.child}); @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ const FlutterLogo(size: 80), // 让child占据剩余屏幕空间 Expanded(child: child), ], ), ); } }
- 更新BodyWidget,将Column替换为ListView以支持滚动:
class BodyWidget extends StatelessWidget { const BodyWidget({super.key}); @override Widget build(BuildContext context) { return ListView( children: List.generate(100, (index) { return Text( "Any text ${index + 1}", style: Theme.of(context).textTheme.headlineSmall, ); }), ); } }
这种方案符合Flutter的布局约束规范,适配性更好,同时保持了组件的可复用性。
方案二:给ShellRoute的child添加明确高度约束
如果不想修改BaseWidget和BodyWidget的结构,可以手动计算可用高度并约束Navigator的尺寸:
ShellRoute( builder: (context, state, child) { final screenHeight = MediaQuery.of(context).size.height; final statusBarHeight = MediaQuery.of(context).padding.top; final logoHeight = 80.0; // 计算child可用的高度:屏幕高度 - 状态栏高度 - Logo高度 final availableHeight = screenHeight - statusBarHeight - logoHeight; return BaseWidget( child: SizedBox( height: availableHeight, child: child, ), ); }, routes: [ GoRoute( path: "/shell", builder: (context, state) => const BodyWidget(), ) ] )
这种方案无需改动原有组件,但需要手动处理高度计算,适配不同设备时需要额外注意。
总结
问题并非go_router的bug,而是布局约束冲突导致的。核心是ShellRoute的Navigator需要明确的高度约束,而原有的ListView布局无法提供这种约束。通过调整布局结构或手动添加约束即可解决问题。
内容的提问来源于stack exchange,提问作者Marcelo Barros

