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

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:12
  • Another 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,
                );
              }),
            ),
          ),
        ));
  }
} 

报错根源

  1. 垂直方向无约束:GridView作为可滚动组件,默认需要明确的垂直空间约束。当它嵌套在Row中时,Row的垂直方向没有提供固定高度或约束,导致GridView无法确定自身高度,触发Vertical viewport was given unbounded height错误。
  2. Expanded使用错误:GridContentView中把Expanded放在SizedBox内部,Expanded仅在Flex布局(Row/Column)中生效,此处的Expanded无法发挥空间分配作用。
  3. 滚动组件嵌套冲突: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 01:21:03