常见的 Flutter 错误

简介

此页面解释了几个经常遇到的 Flutter 框架错误(包括布局错误),并给出了解决这些错误的建议。这是一份不断更新的文档,将在未来的修订版中添加更多错误,欢迎您做出贡献。请随时提交问题提交拉取请求,让此页面对您和 Flutter 社区更有用。

“A RenderFlex overflowed…”

RenderFlex 溢出是最常遇到的 Flutter 框架错误之一,您可能已经遇到过它。

错误是什么样的?

发生时,会出现黄色和黑色条纹,表示应用程序 UI 中的溢出区域。此外,调试控制台中会显示一条错误消息

The following assertion was thrown during layout:
A RenderFlex overflowed by 1146 pixels on the right.

The relevant error-causing widget was

    Row      lib/errors/renderflex_overflow_column.dart:23

The overflowing RenderFlex has an orientation of Axis.horizontal.
The edge of the RenderFlex that is overflowing has been marked in the rendering 
with a yellow and black striped pattern. This is usually caused by the contents 
being too big for the RenderFlex.
(Additional lines of this message omitted)

您可能会如何遇到此错误?

ColumnRow 具有未在其大小中受约束的子小部件时,经常会发生此错误。例如,下面的代码片段演示了一个常见场景

Widget build(BuildContext context) {
  return Row(
    children: [
      const Icon(Icons.message),
      Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Title', style: Theme.of(context).textTheme.headlineMedium),
          const Text(
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed '
            'do eiusmod tempor incididunt ut labore et dolore magna '
            'aliqua. Ut enim ad minim veniam, quis nostrud '
            'exercitation ullamco laboris nisi ut aliquip ex ea '
            'commodo consequat.',
          ),
        ],
      ),
    ],
  );
}

在上面的示例中,Column 尝试比 Row(其父级)可以分配给它的空间更宽,从而导致溢出错误。Column 为什么尝试这样做?要理解这种布局行为,您需要知道 Flutter 框架如何执行布局

为了执行布局,Flutter 以深度优先遍历方式遍历渲染树,并从父级传递大小约束给子级……子级通过向上传递大小给其父级对象,以满足父级建立的约束。” – Flutter 架构概述

在这种情况下,Row 窗口小部件不会限制其子项的大小,Column 窗口小部件也不会。由于缺少其父窗口小部件的约束,第二个 Text 窗口小部件会尝试与它需要显示的所有字符一样宽。然后,Column 会采用 Text 窗口小部件自定的宽度,这与它的父窗口小部件 Row 窗口小部件可以提供的最大水平空间量冲突。

如何解决?

嗯,你需要确保 Column 不会尝试比它所能的更宽。要实现此目的,你需要限制它的宽度。一种方法是将 Column 包裹在 Expanded 窗口小部件中

return const Row(
  children: [
    Icon(Icons.message),
    Expanded(
      child: Column(
          // code omitted
          ),
    ),
  ],
);

另一种方法是将 Column 包裹在 Flexible 窗口小部件中并指定 flex 因子。事实上,Expanded 窗口小部件等同于 Flexible 窗口小部件,其 flex 因子为 1.0,正如 其源代码 所示。要进一步了解如何在 Flutter 布局中使用 Flex 窗口小部件,请查看 此 90 秒的本周窗口小部件视频,内容是关于 Flexible 窗口小部件。

更多信息

下面链接的资源提供了有关此错误的更多信息。

“RenderBox 未布局”

虽然此错误很常见,但它通常是渲染管道中较早发生的某个主要错误的副作用。

错误是什么样的?

错误显示的消息如下所示

RenderBox was not laid out: 
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE

您可能会如何遇到此错误?

通常,问题与违反框约束有关,需要通过向 Flutter 提供更多信息来解决,说明您希望如何约束相关小部件。您可以在 了解约束 页面上了解有关约束在 Flutter 中如何工作的更多信息。

RenderBox was not laid out 错误通常由另外两个错误之一引起

  • “Vertical viewport was given unbounded height”
  • “An InputDecorator…cannot have an unbounded width”

“垂直视口被赋予无界高度”

这是在 Flutter 应用中创建 UI 时可能遇到的另一个常见布局错误。

错误是什么样的?

错误显示的消息如下所示

The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.

Viewports expand in the scrolling direction to fill their container. 
In this case, a vertical viewport was given an unlimited amount of 
vertical space in which to expand. This situation typically happens when a 
scrollable widget is nested inside another scrollable widget.
(Additional lines of this message omitted)

您可能会如何遇到此错误?

当将 ListView(或其他类型的可滚动小部件,如 GridView)放置在 Column 中时,通常会导致此错误。除非 ListView 受其父小部件约束,否则它将占用所有可用的垂直空间。但是,Column 默认情况下不会对子元素的高度施加任何约束。两种行为的结合导致无法确定 ListView 的大小。

Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        const Text('Header'),
        ListView(
          children: const <Widget>[
            ListTile(
              leading: Icon(Icons.map),
              title: Text('Map'),
            ),
            ListTile(
              leading: Icon(Icons.subway),
              title: Text('Subway'),
            ),
          ],
        ),
      ],
    ),
  );
}

如何解决?

要修复此错误,请指定 ListView 的高度。要使其与 Column 中的剩余空间一样高,请使用 Expanded 小部件将其包装(如下例所示)。否则,使用 SizedBox 小部件指定绝对高度,或使用 Flexible 小部件指定相对高度。

Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        const Text('Header'),
        Expanded(
          child: ListView(
            children: const <Widget>[
              ListTile(
                leading: Icon(Icons.map),
                title: Text('Map'),
              ),
              ListTile(
                leading: Icon(Icons.subway),
                title: Text('Subway'),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

更多信息

下面链接的资源提供了有关此错误的更多信息。

“InputDecorator…不能具有无界宽度”

错误消息表明它也与框约束有关,了解框约束对于避免许多最常见的 Flutter 框架错误非常重要。

错误是什么样的?

错误显示的消息如下所示

The following assertion was thrown during performLayout():
An InputDecorator, which is typically created by a TextField, cannot have an 
unbounded width.
This happens when the parent widget does not provide a finite width constraint. 
For example, if the InputDecorator is contained by a `Row`, then its width must 
be constrained. An `Expanded` widget or a SizedBox can be used to constrain the 
width of the InputDecorator or the TextField that contains it.
(Additional lines of this message omitted)

您可能会如何遇到此错误?

例如,当 Row 包含 TextFormFieldTextField,但后者没有宽度约束时,就会发生此错误。

Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Unbounded Width of the TextField'),
      ),
      body: const Row(
        children: [
          TextField(),
        ],
      ),
    ),
  );
}

如何解决?

如错误消息所建议的那样,使用 ExpandedSizedBox 小部件来约束文本字段,从而修复此错误。以下示例演示如何使用 Expanded 小部件

Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Unbounded Width of the TextField'),
      ),
      body: Row(
        children: [
          Expanded(child: TextFormField()),
        ],
      ),
    ),
  );
}

“不正确的 ParentData 小部件用法”

此错误是关于缺少预期的父小部件。

错误是什么样的?

错误显示的消息如下所示

The following assertion was thrown while looking for parent data:
Incorrect use of ParentDataWidget.
(Some lines of this message omitted)
Usually, this indicates that at least one of the offending ParentDataWidgets 
listed above is not placed directly inside a compatible ancestor widget.

您可能会如何遇到此错误?

虽然 Flutter 的小部件在如何在 UI 中组合在一起方面通常很灵活,但其中一小部分小部件需要特定的父小部件。当您的部件树中无法满足此预期时,您可能会遇到此错误。

以下是不完整的在 Flutter 框架中需要特定父小部件的小部件列表。欢迎使用页面右上角的文档图标提交 PR(以扩展此列表)。

小部件 预期的父小部件
Flexible RowColumnFlex
Expanded(专门的 Flexible RowColumnFlex
Positioned Stack
TableCell Table

如何解决?

一旦您知道缺少哪个父小部件,修复方法就显而易见了。

“在构建期间调用 setState”

您的 Flutter 代码中的 build 方法不是直接或间接调用 setState 的好地方。

错误是什么样的?

当错误发生时,控制台中会显示以下消息

The following assertion was thrown building DialogPage(dirty, dependencies: 
[_InheritedTheme, _LocalizationsScope-[GlobalKey#59a8e]], 
state: _DialogPageState#f121e):
setState() or markNeedsBuild() called during build.

This Overlay widget cannot be marked as needing to build because the framework 
is already in the process of building widgets.
(Additional lines of this message omitted)

您可能会如何遇到此错误?

通常,当在 build 方法中调用 setState 方法时,就会发生此错误。

发生此错误的常见情况是在 build 方法中尝试从 Dialog 中触发。这通常是由于需要立即向用户显示信息,但绝不应从 build 方法中调用 setState

以下代码段似乎是此错误的常见罪魁祸首

Widget build(BuildContext context) {
  // Don't do this.
  showDialog(
      context: context,
      builder: (context) {
        return const AlertDialog(
          title: Text('Alert Dialog'),
        );
      });

  return const Center(
    child: Column(
      children: <Widget>[
        Text('Show Material Dialog'),
      ],
    ),
  );
}

此代码不会显式调用 setState,但它由 showDialog 调用。 build 方法不是调用 showDialog 的合适位置,因为框架可以为每一帧调用 build,例如在动画期间。

如何解决?

避免此错误的一种方法是使用 Navigator API 将对话框作为路由触发。在以下示例中,有两个页面。第二个页面有一个在进入时显示的对话框。当用户通过单击第一个页面上的按钮请求第二个页面时,Navigator 会推送两个路由——一个用于第二个页面,另一个用于对话框。

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('First Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text('Launch screen'),
          onPressed: () {
            // Navigate to the second screen using a named route.
            Navigator.pushNamed(context, '/second');
            // Immediately show a dialog upon loading the second screen.
            Navigator.push(
              context,
              PageRouteBuilder(
                barrierDismissible: true,
                opaque: false,
                pageBuilder: (_, anim1, anim2) => const MyDialog(),
              ),
            );
          },
        ),
      ),
    );
  }
}

ScrollController 附加到多个滚动视图

当多个滚动小组件(如 ListView)同时出现在屏幕上时,可能会发生此错误。此错误更可能发生在 Web 或桌面应用程序中,而不是移动应用程序中,因为在移动设备上很少遇到这种情况。

有关更多信息以及如何修复,请查看以下有关 PrimaryScrollController 的视频

参考资料

要详细了解如何调试错误,尤其是 Flutter 中的布局错误,请查看以下资源