副标题:探讨其发生的原因和解决的方法
原文作者:medium.com/@ktjlee
发布时间:2020年7月27日 - 9分钟阅读
注意:熟悉
Row
、Column
和Expanded
对本文是有用的,但不是必需的。
作为一个Flutter开发者,你可能遇到过在你的应用上图像被切断(或者甚至不可见)的问题。也许你得到了 "viewport was given unbounded height "的错误。" 事实上,最常见的两种Flutter错误都是布局的问题:widget溢出和 "renderbox未布局 "问题。遇到布局问题的不止你一个人,但是,你该去哪里解决呢?
幸运的是,Dart DevTool的Flutter Inspector可以帮助你了解它们发生的原因,也可以帮助你解决这些问题。在本文中,你将通过调试3个常见的布局问题来学习如何使用该工具。所以,下次遇到问题时,你可以像专家一样解决它!
目录
- 什么是Flutter Inspector?
- 调试冒险
- 溢出
- 无限高误差
- 隐形垂直分割器
- 概要
什么是Flutter Inspector?
Flutter Inspector是一个探索和可视化部件树的工具(Dart DevTools套件的一部分)。它是发现你的应用程序布局的来龙去脉的完美工具。在下面的GIF中看一看这个工具。
GIF显示Flutter Inspector的Details Tree和Layout Explorer功能。
通过检查器,你可以选择应用程序上的widget,甚至可以删除调试横幅。无论你是想知道为什么你的widget是不可见的,还是想知道向Row的子代添加flex如何影响UI,它都能帮你解决。这篇文章主要介绍了以下功能:
- 细节树--使您能够检查每个widget的属性。您可以检查一个widget的实际大小,并查看约束是如何从父widget传递下来的。
- 布局资源管理器 - 让您能够可视化
Flex
widget(Row
、Column
、Flex
)及其子节点。调整Flex、贴合和轴对齐方式,可以让您在运行的应用程序上看到变化。
在这一点上,你可能会想知道,"我如何尝试这个厉害的工具?" 只要在应用程序上运行Dart DevTools,检查器就是你看到的第一个工具。更多信息,请看如何从IDE或命令行运行Dart DevTools。
调试大冒险🤠
让我们从一个包含3个布局问题的演示应用开始,你将调试这些问题。你将使用Flutter Inspector分别调试每一个问题,并在最后结合修复的问题来完成一个简单的菜单应用,它看起来就像下面的截图一样
结束菜单应用与修复的问题
在处理布局问题时,请使用以下步骤。为了帮助您记忆,请使用COIN缩写。这个词是谁发明的?是我,就在刚才。
- 检查调试控制台上的错误信息,以确定错误类型和导致错误的widget。
- 打开 "布局资源管理器",以可视化的
Flex
widget和它的孩子。 - 检查导致错误的widget的大小和限制,以及它的父/子与细节树。
- 导航回到你的代码,并修复这个问题。
如果你在自己的电脑上跟着文章的其余部分走,那是最好的。所以,打开你最喜欢的文本编辑器或IDE,让我们一起去冒险吧!
- 创建一个名为
menu
的新Flutter项目。
$ flutter create menu
- 替换
lib/main.dart
文件内容。 每一个布局问题在代码中都分离到自己的Example
类中,从应用主体中的Example1
开始。用以下代码替换lib/main.dart
:
import 'package:flutter/material.dart';
void main() {
runApp(Menu());
}
class MenuItem extends StatelessWidget {
const MenuItem(this.icon, this.itemText);
final String icon;
final String itemText;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Text(
icon,
style: TextStyle(
fontSize: 30.0,
),
),
title: Text(itemText),
);
}
}
class Menu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Menu Demo'),
),
body: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
children: [
// Modify code here
Example1(),
],
),
),
),
);
}
}
// Problem 1: Overflow error
class Example1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 30.0),
child: Row(
children: [
Text(
'Explore the restaurant\'s delicious menu items below!',
style: TextStyle(
fontSize: 18.0,
),
),
],
),
);
}
}
// Problem 2: Viewport was given unbounded height error
class Example2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: [
MenuItem('🍔', 'Burger'),
MenuItem('🌭', 'Hot Dog'),
MenuItem('🍟', 'Fries'),
MenuItem('🥤', 'Soda'),
MenuItem('🍦', 'Ice Cream'),
],
);
}
}
// Problem 3: Invisible VerticalDivider
class Example3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
print('Pickup button pressed.');
},
child: Text(
'Pickup',
),
),
// This widget is not shown on screen initially.
VerticalDivider(
width: 20.0,
thickness: 5.0,
),
RaisedButton(
onPressed: () {
print('Delivery button pressed.');
},
child: Text(
'Delivery',
),
)
],
);
}
}
- 运行该应用程序。
- 打开Dart DevTools。
布局问题1:溢出错误
当你运行该应用时,你会看到线的尽头有一个黄黑斜条框,类似于警戒线。
这意味着存在溢出错误--最常见的Flutter布局错误。现在,让我们按照调试步骤来找出问题并找到正确的修复方法。
- 检查控制台上的错误信息。
调试控制台上的溢出错误
首先,确定是哪个widget造成的问题 该错误信息表明main.dart
第54行的Row
造成了问题,因为Row
是Flex
widget(意味着Row
扩展了Flex
类),你可以用布局资源管理器检查它。
- 打开布局资源管理器。
导航到DevTools,打开Layout Explorer标签。
布局资源管理器上的溢出错误
单击 "Row"。图片上的数字与下面的步骤相关联)。
-
底部出现了红色的横幅,说明有问题。当您仔细观察这些横幅时,您意识到Text(宽度=447)比父部件Row(宽度=335)更宽,导致溢出错误。
-
你需要一个方法来告诉Text,它只能和Row一样宽,不能再多了。尝试将Text的flex调整为1。(这类似于用
Expanded
包装Text
。)结果,Text缩小了,红色横幅也消失了。唷,看起来好像修好了。不完全是! 你仍然必须更新代码,因为这个工具不会触及你的代码。它只是显示如果你改变一些布局属性会发生什么。
旁白。你可能会想,为什么不是所有的
Row
和Column
的子代都默认为Expanded
?Flutter团队做出了这个设计决定。如果所有的子代都默认为
Expanded
,就会产生其他布局问题,比如一些子代被过度挤压或拉伸。
-
用细节树检查大小和约束。 在这种情况下,你可以跳过这一步,因为问题已经被发现。
-
导航回到代码中,并修复它。
在VS Code上使用智能重构来包装文本(其他编辑器上也有类似的方法)。
用Expanded
包裹Text
。默认的flex是1,所以你不必指定这个属性。
布局问题2:高度无限制的错误
我们继续下一个例子,把Column里面的Example1()
替换成Example2()
,然后热重载。
Column(
children: [
// Modify code here
Example2(),
],
)
尽管在Example2
类中有一个带有各种菜单项的ListView
,但在应用程序上什么都不显示:
到底发生了什么?
- 检查控制台的错误信息。
调试控制台上的高度不受限制错误
main.dart
的第72行的ListView
导致了 "Vertical viewport was given unbounded height "的错误。乍一看,垂直视口和无界这两个词还不清楚,所以继续下一步。
- 打开 "布局资源管理器"。
导航回到DevTools,打开 "布局资源管理器" 选项卡。
布局资源管理器不显示孙子的Flex小部件。
通过点击顶部的刷新图标来刷新树。点击ListView后什么都没有出现,因为布局资源管理器只支持Flex部件及其直接子代。有趣的是,点击Example2和Column也没有任何作用--布局资源管理器仍然是空白的。继续下一步。
- 用细节树检查大小和约束。
ListView对Details树的约束和大小。
展开ListView
的第一个renderObject
,它包含了绘制小组件的信息。
- 橙色的文字表示尺寸缺失--难怪
ListView
在应用中缺失。 - 在查看
constraints
属性后,注意到高度约束被列为无穷大。现在错误信息更有意义了。ListView
是一个 "视口",在其滚动方向上被赋予了一个无约束--换句话说是无限的--高度。
约束是由父部件传递下来的。所以,这里是widget如何确定其约束的快照。
Column
: 你想占多少高度就占多少高度
ListView
: 好的,我会占用所有的空间。
Column
: 哇,但那就像无穷大一样,伙计。
而且,widgets不知道该怎么办......大小无法确定,因为ListView
要的是无限高,这在屏幕上无法描绘。
旁白。为什么
Column
不将其子代的高度限制在自己的高度内?你可能会出现这样的情况:第一个子代占据了所有空间,迫使第二个子代的高度为0。而且你不会立即知道,因为不会抛出错误。
- 导航回你的代码并修复它。
class Example2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView(
...
),
);
}
}
还记得前面说过,用 Expanded
包裹一个 widget 会给它提供一个沿父体主轴的_无限制_约束(宽度为 Row
,高度为 Column
)。在本例中,父节点是Column
,所以Expanded
提供了一个高度约束。用 Expanded
包裹 ListView
,然后热重载,就可以看到列表显示在应用上了。
布局问题3:不可见的垂直分割器
现在,将Column
中的Example2()
替换为Example3()
。
Column(
children: [
// Modify code here
Example3(),
],
)
仔细看一下示Example3
类的代码。注意到VerticalDivider
是存在的,但热重载后应用上只出现了两个按钮:
为什么·VerticalDivider·看不见?
-
检查控制台的错误信息。 这次你没有收到任何错误信息。继续进行下一步。
-
打开 "布局资源管理器"。 导航回到DevTools,并单击 "布局资源管理器" 选项卡。
布局资源管理器上的垂直分割器
刷新树后,单击VerticalDivider并滚动到布局资源管理器的右侧。观察到VerticalDivider的宽度和高度是没有限制的。
1. 注意到**VerticalDivider**的高度是0,这就解释了为什么它没有显示在应用程序上。
2. 像之前那样,将flex切换为1。在这种情况下,用`Expanded`包装一个`VerticalDivider`是不行的,因为`Expanded`提供了一个宽度约束,而不是高度约束。
3. 接下来你可能会尝试将分隔符的高度拉伸到凸起按钮的高度,所以尝试将横轴对齐设置为拉伸。高度仍然为0,所以继续下一步。
3. 用细节树检查尺寸和约束。
使用Details Tree检查Row和它的子代。
1. 打开`VerticalDivider`下的第一个`renderObject`。约束属性表明,widget既没有宽度也没有高度约束,与**Layout Explorer**显示的相匹配。但是,在`extraitionalConstraints`下,宽度为20(在示例代码中明确定义),但高度仍然没有约束。宽度不是问题,所以我们把重点放在高度上。
2. 上到父部件`Row`,打开`renderObject`,可以看到`Row`也没有高度约束。
为什么这么说呢?
最重要的是要记住,约束是传下来的。
Column
告诉Row
: 选择你想要的任何高度
Row
告诉VerticalDivider
:选择你想要的任何宽度。选择你想要的任何宽度 因为Column
告诉我选择我的高度,所以你也可以选择自己的高度。
VerticalDivider
: 所以我的宽度是20。我可以选择我的高度,所以我默认为0。
- 导航回你的代码,并修复它。
class Example3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
height: 50.0,
child: Row(
...
),
);
}
}
为了让VerticalDivider
有一个高度,必须给出一个高度约束。将 Row
包裹在 SizedBox
中,并给它一个 50.0 的固定高度。这样做会强制Row向VerticalDivider
传递一个高度约束。
热重载。瞧!VerticalDivider
彈出來了。VerticalDivider在屏幕上弹出。
旁白:
VerticalDivider
的行为与ListView
不同,因为它们的定义很独特。当被告知要选择自己的高度时,ListView
希望尽可能高,而》VerticalDivider
希望尽可能短。但是,两者都需要一个高度约束才能正确地出现在应用程序中!
现在,让我们把这3个例子中的固定代码一起放在Column()
里面:
Column(
children: [
// Modify code here
Example1(),
Example2(),
Example3(),
],
)
热重装。恭喜你,你完成了菜单应用!
小结
通过本教程,你学会了。
- 约束是通过小组件树向下传递的。
Expanded
为Row
或Column
的子节点提供约束。- Flutter Inspector是你处理布局问题时最好的朋友。
要了解更多,请查看flutter.dev上的理解约束。
祝你调试愉快!
关于作者:凯蒂是密歇根大学的大四学生,学习计算机科学。她目前是Flutter开发者关系团队的实习生,帮助开发者学习和构建很棒的应用程序。要了解她的工作,请访问她的GitHub和LinkedIn。
通过( www.DeepL.com/Translator )(免费版)翻译