常见的 Flutter 错误
简介
#本页面解释了几个常见的 Flutter 框架错误(包括布局错误),并提供了解决方法建议。这是一个动态文档,未来版本中将添加更多错误,欢迎您的贡献。请随时提交问题 或提交拉取请求 以使此页面对您和 Flutter 社区更有用。
运行应用时出现纯红色或灰色屏幕
#通常称为“红色(或灰色)死亡屏幕”,这是 Flutter 通知您存在错误的一种方式。
红色屏幕可能出现在应用以调试或概要分析模式运行时。灰色屏幕可能出现在应用以发布模式运行时。
通常,这些错误发生在出现未捕获的异常(您可能需要另一个 try-catch 块)或出现某些渲染错误(例如溢出错误)时。
以下文章提供了一些关于调试此类错误的有用见解:
- Flutter 错误详解 作者:Abishek
- 理解和解决 Flutter 中的灰色屏幕 作者:Christopher Nwosu-Madueke
- Flutter 卡在白屏上 作者:Kesar Bhimani
“RenderFlex 溢出…”
#RenderFlex 溢出是 Flutter 框架中最常见的错误之一,您可能已经遇到过。
错误是什么样的?
发生这种情况时,会出现黄色和黑色的条纹,指示应用程序 UI 中溢出的区域。此外,调试控制台中还会显示错误消息:
布局期间抛出以下断言:
RenderFlex 向右溢出 1146 像素。
相关的导致错误的小部件是
Row lib/errors/renderflex_overflow_column.dart:23
溢出的 RenderFlex 的方向为 Axis.horizontal。
溢出的 RenderFlex 边缘已在渲染中用黄色和黑色条纹图案标记。这通常是由内容太大而无法容纳在 RenderFlex 中引起的。
(省略此消息的其他行)
您可能会如何遇到此错误?
当 Column
或 Row
具有大小不受约束的子小部件时,经常会发生此错误。例如,下面的代码片段演示了一个常见场景:
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
小部件试图与其需要显示的所有字符一样宽。然后,Text
小部件的自定宽度被 Column
采用,这与它的父级 Row
小部件所能提供的最大水平空间冲突。
如何修复?
好吧,您需要确保 Column
不会尝试比它所能容纳的更宽。要实现这一点,您需要约束其宽度。一种方法是用 Expanded
小部件包装 Column
:
return const Row(
children: [
Icon(Icons.message),
Expanded(
child: Column(
// 代码省略
),
),
],
);
另一种方法是用 Flexible
小部件包装 Column
并指定 flex
系数。事实上,Expanded
小部件相当于 flex
系数为 1.0 的 Flexible
小部件,如其源代码 所示。要进一步了解如何在 Flutter 布局中使用 Flex
小部件,请查看有关 Flexible
小部件的这个 90 秒的每周小部件视频。
更多信息:
下面链接的资源提供了有关此错误的更多信息。
“RenderBox 未布局”
#虽然此错误很常见,但它通常是渲染管道早期发生的 主要错误 的副作用。
错误是什么样的?
错误显示的消息如下所示:
RenderBox 未布局:
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
您可能会如何遇到此错误?
通常,问题与违反边框约束有关,需要通过向 Flutter 提供更多信息来解决,说明您希望如何约束相关的小部件。您可以在理解约束 页面上了解有关 Flutter 中约束如何工作的更多信息。
RenderBox 未布局
错误通常是由以下两个错误之一引起的:
- “垂直视口被赋予了无限高度”
- “InputDecorator……不能具有无限宽度”
“垂直视口被赋予了无限高度”
#这是您在 Flutter 应用中创建 UI 时可能会遇到的另一个常见布局错误。
错误是什么样的?
错误显示的消息如下所示:
performResize() 期间抛出以下断言:
垂直视口被赋予了无限高度。
视口在滚动方向上展开以填充其容器。
在这种情况下,垂直视口被赋予了无限量的垂直空间来展开。这种情况通常发生在可滚动小部件嵌套在另一个可滚动小部件内时。
(省略此消息的其他行)
您可能会如何遇到此错误?
当 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 框架错误非常重要。
错误是什么样的?
错误显示的消息如下所示:
performLayout() 期间抛出以下断言:
InputDecorator(通常由 TextField 创建)不能具有无限宽度。
当父小部件未提供有限宽度约束时,就会发生这种情况。例如,如果 InputDecorator 包含在 `Row` 中,则其宽度必须受约束。可以使用 `Expanded` 小部件或 SizedBox 来约束 InputDecorator 或包含它的 TextField 的宽度。
(省略此消息的其他行)
您可能会如何遇到此错误?
例如,当 Row
包含 TextFormField
或 TextField
但后者没有宽度约束时,就会发生此错误。
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('TextField 的无限宽度'),
),
body: const Row(
children: [
TextField(),
],
),
),
);
}
如何修复?
如错误消息所示, 使用 Expanded
或 SizedBox
小部件约束文本字段来修复此错误。以下示例演示了如何使用 Expanded
小部件:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('TextField 的无限宽度'),
),
body: Row(
children: [
Expanded(child: TextFormField()),
],
),
),
);
}
“不正确地使用了 ParentData 小部件”
#此错误与缺少预期的父小部件有关。
错误是什么样的?
错误显示的消息如下所示:
查找父数据时抛出以下断言:
不正确地使用了 ParentDataWidget。
(此消息的某些行已省略)
通常,这表示上面列出的至少一个有问题的 ParentDataWidgets 没有直接放在兼容的祖先小部件内。
您可能会如何遇到此错误?
虽然 Flutter 的小部件在 UI 中的组合方式通常很灵活,但一小部分小部件需要特定的父小部件。当您的窗口小部件树中无法满足此期望时,您可能会遇到此错误。
以下是 Flutter 框架中需要特定父小部件的小部件的不完整列表。请随时提交 PR(使用页面右上角的文档图标)来扩展此列表。
小部件 | 预期的父小部件(s) |
---|---|
Flexible | Row 、Column 或 Flex |
Expanded (一个专门的 Flexible ) | Row 、Column 或 Flex |
Positioned | Stack |
TableCell | Table |
如何修复?
一旦您知道缺少哪个父小部件,修复方法应该很明显。
“在 build 期间调用了 setState”
#在您的 Flutter 代码中,build
方法不是直接或间接调用 setState
的好地方。
错误是什么样的?
当错误发生时,控制台会显示以下消息:
构建 DialogPage(dirty, dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#59a8e]], state: _DialogPageState#f121e) 时抛出以下断言:
在 build 期间调用了 setState() 或 markNeedsBuild()。
此 Overlay 小部件不能标记为需要构建,因为框架已经在构建小部件的过程中。
(此消息的其他行已省略)
您可能会如何遇到此错误?
通常,此错误发生在 setState
方法在 build
方法中调用时。
发生此错误的一个常见场景是尝试从 build
方法中触发 Dialog
。这通常是由于需要立即向用户显示信息,但是 setState
绝不应该从 build
方法中调用。
以下代码片段似乎是此错误的常见罪魁祸首:
Widget build(BuildContext context) {
// 不要这样做。
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: () {
// 使用命名路由导航到第二个屏幕。
Navigator.pushNamed(context, '/second');
// 在加载第二个屏幕时立即显示对话框。
Navigator.push(
context,
PageRouteBuilder(
barrierDismissible: true,
opaque: false,
pageBuilder: (_, anim1, anim2) => const MyDialog(),
),
);
},
),
),
);
}
}
ScrollController 附加到多个滚动视图
#当多个滚动小部件(例如 ListView
)同时出现在屏幕上时,可能会发生此错误。对于 Web 或桌面应用程序,此错误更容易发生,因为在移动应用程序上很少遇到这种情况。
有关更多信息以及如何修复,请查看以下有关 PrimaryScrollController
的视频:
参考
#要了解有关如何调试错误(尤其是在 Flutter 中的布局错误)的更多信息,请查看以下资源:
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。