Flutter Web中Row嵌套GridView报错的问题求助
解决Flutter Web中GridView嵌套Row的布局报错问题
问题说明
在Flutter Web开发中,将GridView嵌套到Row组件内时出现以下报错:
Another exception was thrown: Vertical viewport was given unbounded height.Another exception was thrown: Assertion failed: file:///C:/dev/flutter/packages/flutter/lib/src/rendering/box.dart:1972:12Another exception was thrown: Cannot hit test a render box that has never been laid out.
尝试使用Expanded和SizedBox组件后仍未解决,相关代码如下:
import 'package:cleanvideos/extensions/theme_extesion.dart'; import 'package:cleanvideos/extensions/widget_layout_extension.dart'; import 'package:cleanvideos/models/contents/video_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; class ContentViewBuilder extends StatefulWidget { final String? title; final String? subtitle; final String? label; final List<ModelVideo> videos; final EdgeInsets? titlePadding; const ContentViewBuilder({ super.key, this.title, this.subtitle, this.label, required this.videos, this.titlePadding, }); @override State<ContentViewBuilder> createState() => _ContentViewBuilderState(); } class _ContentViewBuilderState extends State<ContentViewBuilder> { bool isAudioView = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), //TODO Implementar AppBar body: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( widget.label ?? "", style: context.textTheme.titleMedium?.copyWith(color: Colors.grey), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title ?? "", style: context.textTheme.titleLarge ?.copyWith(fontWeight: FontWeight.bold)), Text( widget.subtitle ?? "", style: context.textTheme.titleLarge ?.copyWith(color: Colors.grey), ) ], ).withPadding(const EdgeInsets.only(left: 16)) ], ).withPadding(widget.titlePadding ?? const EdgeInsets.only(left: 16)), const Divider( height: 0, color: Color(0xff0038B8), ).withPadding(const EdgeInsets.only(top: 16)), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( mainAxisAlignment: MainAxisAlignment.start, children: [ FilledButton( style: FilledButton.styleFrom( backgroundColor: !isAudioView ? const Color(0xffebeffa) : Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), fixedSize: const Size(114, 43)), onPressed: () { setState(() { isAudioView = false; }); }, child: Text( "Videos", style: TextStyle( color: !isAudioView ? const Color(0xff0038B8) : const Color(0xff7f9bdb)), )), FilledButton( style: FilledButton.styleFrom( backgroundColor: isAudioView ? const Color(0xffebeffa) : Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), fixedSize: const Size(114, 43)), onPressed: () { setState(() { isAudioView = true; }); }, child: Text( "Músicas", style: TextStyle( color: isAudioView ? const Color(0xff0038B8) : const Color(0xff7f9bdb)), )) ], ).withPadding( const EdgeInsets.only(left: 62, top: 47, right: 24)), const SizedBox( height: 691, child: VerticalDivider( thickness: 1, indent: 0, endIndent: 0, color: Color(0xff0038B8), ), ), ], ) ], ), ); } } class GridContentView extends StatelessWidget { final bool isAudioView; const GridContentView({super.key, required this.isAudioView}); @override Widget build(BuildContext context) { return SizedBox( width: 1140, child: Expanded( child: SingleChildScrollView( child: GridView.count( crossAxisCount: isAudioView ? 5 : 3, crossAxisSpacing: 16, mainAxisSpacing: 16, children: List.generate(10, (index) { return Container( height: 300, width: isAudioView ? 200 : 500, color: Colors.red, ); }), ), ), )); } }
报错根源
- 垂直方向无约束:GridView作为可滚动组件,默认需要明确的垂直空间约束。当它嵌套在Row中时,Row的垂直方向没有提供固定高度或约束,导致GridView无法确定自身高度,触发
Vertical viewport was given unbounded height错误。 - Expanded使用错误:
GridContentView中把Expanded放在SizedBox内部,Expanded仅在Flex布局(Row/Column)中生效,此处的Expanded无法发挥空间分配作用。 - 滚动组件嵌套冲突:SingleChildScrollView和GridView都是可滚动组件,嵌套后会导致滚动逻辑冲突,同时GridView本身支持垂直滚动,无需额外包裹SingleChildScrollView。
修复方案
1. 调整Row内的布局结构
在ContentViewBuilder的Row中,给GridContentView所在的位置添加Expanded,让它占据Row的剩余水平空间;同时给GridView所在的容器设置明确的垂直约束(比如用Expanded包裹)。
2. 修改GridContentView组件
- 移除无用的SingleChildScrollView和错误使用的Expanded
- 给GridView添加
shrinkWrap: true,让它根据子项高度自适应收缩 - 若需要GridView内部滚动,保留
physics: const AlwaysScrollableScrollPhysics()(可选,根据需求调整)
修复后的完整代码
import 'package:cleanvideos/extensions/theme_extesion.dart'; import 'package:cleanvideos/extensions/widget_layout_extension.dart'; import 'package:cleanvideos/models/contents/video_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; class ContentViewBuilder extends StatefulWidget { final String? title; final String? subtitle; final String? label; final List<ModelVideo> videos; final EdgeInsets? titlePadding; const ContentViewBuilder({ super.key, this.title, this.subtitle, this.label, required this.videos, this.titlePadding, }); @override State<ContentViewBuilder> createState() => _ContentViewBuilderState(); } class _ContentViewBuilderState extends State<ContentViewBuilder> { bool isAudioView = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), //TODO Implementar AppBar body: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( widget.label ?? "", style: context.textTheme.titleMedium?.copyWith(color: Colors.grey), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title ?? "", style: context.textTheme.titleLarge ?.copyWith(fontWeight: FontWeight.bold)), Text( widget.subtitle ?? "", style: context.textTheme.titleLarge ?.copyWith(color: Colors.grey), ) ], ).withPadding(const EdgeInsets.only(left: 16)) ], ).withPadding(widget.titlePadding ?? const EdgeInsets.only(left: 16)), const Divider( height: 0, color: Color(0xff0038B8), ).withPadding(const EdgeInsets.only(top: 16)), // 用Expanded让Row占据Column剩余垂直空间,给GridView提供垂直约束 Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( mainAxisAlignment: MainAxisAlignment.start, children: [ FilledButton( style: FilledButton.styleFrom( backgroundColor: !isAudioView ? const Color(0xffebeffa) : Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), fixedSize: const Size(114, 43)), onPressed: () { setState(() { isAudioView = false; }); }, child: Text( "Videos", style: TextStyle( color: !isAudioView ? const Color(0xff0038B8) : const Color(0xff7f9bdb)), )), FilledButton( style: FilledButton.styleFrom( backgroundColor: isAudioView ? const Color(0xffebeffa) : Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), fixedSize: const Size(114, 43)), onPressed: () { setState(() { isAudioView = true; }); }, child: Text( "Músicas", style: TextStyle( color: isAudioView ? const Color(0xff0038B8) : const Color(0xff7f9bdb)), )) ], ).withPadding( const EdgeInsets.only(left: 62, top: 47, right: 24)), const SizedBox( height: 691, child: VerticalDivider( thickness: 1, indent: 0, endIndent: 0, color: Color(0xff0038B8), ), ), // 用Expanded让GridContentView占据Row剩余水平空间 Expanded( child: GridContentView(isAudioView: isAudioView), ).withPadding(const EdgeInsets.only(left:24,top:47)) ], ), ) ], ), ); } } class GridContentView extends StatelessWidget { final bool isAudioView; const GridContentView({super.key, required this.isAudioView}); @override Widget build(BuildContext context) { return GridView.count( shrinkWrap: true, // 保留滚动属性,若不需要内部滚动可改为NeverScrollableScrollPhysics() physics: const AlwaysScrollableScrollPhysics(), crossAxisCount: isAudioView ? 5 : 3, crossAxisSpacing: 16, mainAxisSpacing: 16, children: List.generate(10, (index) { return Container( height: 300, color: Colors.red, ); }), ); } }
关键修复点说明
- 在ContentViewBuilder中,将包含按钮和GridView的Row用
Expanded包裹,让它占据Column的剩余垂直空间,给GridView提供明确的垂直约束。 - GridContentView中移除了冗余的SizedBox、SingleChildScrollView和错误使用的Expanded,添加
shrinkWrap: true让GridView根据子项高度自适应。 - 在Row中用
Expanded包裹GridContentView,确保它获得足够的水平空间来展示内容。
内容的提问来源于stack exchange,提问作者Vinicius Gomes
相关产品推荐
相关产品推荐

