[Flutter翻译]如何使用Flutter Inspector调试布局问题?

2,912 阅读11分钟

副标题:探讨其发生的原因和解决的方法

原文地址:medium.com/flutter/how…

原文作者:medium.com/@ktjlee

发布时间:2020年7月27日 - 9分钟阅读

注意:熟悉RowColumnExpanded对本文是有用的,但不是必需的。

作为一个Flutter开发者,你可能遇到过在你的应用上图像被切断(或者甚至不可见)的问题。也许你得到了 "viewport was given unbounded height "的错误。" 事实上,最常见的两种Flutter错误都是布局的问题:widget溢出和 "renderbox未布局 "问题。遇到布局问题的不止你一个人,但是,你该去哪里解决呢?

幸运的是,Dart DevTool的Flutter Inspector可以帮助你了解它们发生的原因,也可以帮助你解决这些问题。在本文中,你将通过调试3个常见的布局问题来学习如何使用该工具。所以,下次遇到问题时,你可以像专家一样解决它!

目录

  1. 什么是Flutter Inspector?
  2. 调试冒险
    • 溢出
    • 无限高误差
    • 隐形垂直分割器
  3. 概要

什么是Flutter Inspector?

Flutter Inspector是一个探索和可视化部件树的工具(Dart DevTools套件的一部分)。它是发现你的应用程序布局的来龙去脉的完美工具。在下面的GIF中看一看这个工具。

GIF显示Flutter Inspector的Details Tree和Layout Explorer功能。

通过检查器,你可以选择应用程序上的widget,甚至可以删除调试横幅。无论你是想知道为什么你的widget是不可见的,还是想知道向Row的子代添加flex如何影响UI,它都能帮你解决。这篇文章主要介绍了以下功能:

  • 细节树--使您能够检查每个widget的属性。您可以检查一个widget的实际大小,并查看约束是如何从父widget传递下来的。
  • 布局资源管理器 - 让您能够可视化 Flex widget(RowColumnFlex)及其子节点。调整Flex、贴合和轴对齐方式,可以让您在运行的应用程序上看到变化。

在这一点上,你可能会想知道,"我如何尝试这个厉害的工具?" 只要在应用程序上运行Dart DevTools,检查器就是你看到的第一个工具。更多信息,请看如何从IDE或命令行运行Dart DevTools

调试大冒险🤠

让我们从一个包含3个布局问题的演示应用开始,你将调试这些问题。你将使用Flutter Inspector分别调试每一个问题,并在最后结合修复的问题来完成一个简单的菜单应用,它看起来就像下面的截图一样

结束菜单应用与修复的问题

在处理布局问题时,请使用以下步骤。为了帮助您记忆,请使用COIN缩写。这个词是谁发明的?是我,就在刚才。

  1. 检查调试控制台上的错误信息,以确定错误类型和导致错误的widget。
  2. 打开 "布局资源管理器",以可视化的Flex widget和它的孩子。
  3. 检查导致错误的widget的大小和限制,以及它的父/子与细节树
  4. 导航回到你的代码,并修复这个问题。

如果你在自己的电脑上跟着文章的其余部分走,那是最好的。所以,打开你最喜欢的文本编辑器或IDE,让我们一起去冒险吧!

  1. 创建一个名为menu的新Flutter项目。
$ flutter create menu
  1. 替换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',
          ),
        )
      ],
    );
  }
}
  1. 运行该应用程序。
  2. 打开Dart DevTools

布局问题1:溢出错误

当你运行该应用时,你会看到线的尽头有一个黄黑斜条框,类似于警戒线

这意味着存在溢出错误--最常见的Flutter布局错误。现在,让我们按照调试步骤来找出问题并找到正确的修复方法。

  1. 检查控制台上的错误信息。

调试控制台上的溢出错误

首先,确定是哪个widget造成的问题 该错误信息表明main.dart第54行的Row造成了问题,因为RowFlex widget(意味着Row扩展了Flex类),你可以用布局资源管理器检查它。

  1. 打开布局资源管理器。

导航到DevTools,打开Layout Explorer标签。

布局资源管理器上的溢出错误

单击 "Row"。图片上的数字与下面的步骤相关联)。

  1. 底部出现了红色的横幅,说明有问题。当您仔细观察这些横幅时,您意识到Text(宽度=447)比父部件Row(宽度=335)更宽,导致溢出错误。

  2. 你需要一个方法来告诉Text,它只能和Row一样宽,不能再多了。尝试将Text的flex调整为1。(这类似于用Expanded包装Text。)结果,Text缩小了,红色横幅也消失了。唷,看起来好像修好了。不完全是! 你仍然必须更新代码,因为这个工具不会触及你的代码。它只是显示如果你改变一些布局属性会发生什么。

旁白。你可能会想,为什么不是所有的RowColumn的子代都默认为Expanded

Flutter团队做出了这个设计决定。如果所有的子代都默认为Expanded,就会产生其他布局问题,比如一些子代被过度挤压或拉伸。

  1. 用细节树检查大小和约束。 在这种情况下,你可以跳过这一步,因为问题已经被发现。

  2. 导航回到代码中,并修复它。

在VS Code上使用智能重构来包装文本(其他编辑器上也有类似的方法)。

Expanded包裹Text。默认的flex是1,所以你不必指定这个属性。

布局问题2:高度无限制的错误

我们继续下一个例子,把Column里面的Example1()替换成Example2(),然后热重载。

Column(
  children: [
    // Modify code here
    Example2(),
  ],
)

尽管在Example2类中有一个带有各种菜单项的ListView,但在应用程序上什么都不显示:

到底发生了什么?

  1. 检查控制台的错误信息。

调试控制台上的高度不受限制错误

main.dart的第72行的ListView导致了 "Vertical viewport was given unbounded height "的错误。乍一看,垂直视口和无界这两个词还不清楚,所以继续下一步。

  1. 打开 "布局资源管理器"。

导航回到DevTools,打开 "布局资源管理器" 选项卡。

布局资源管理器不显示孙子的Flex小部件。

通过点击顶部的刷新图标来刷新树。点击ListView后什么都没有出现,因为布局资源管理器只支持Flex部件及其直接子代。有趣的是,点击Example2Column也没有任何作用--布局资源管理器仍然是空白的。继续下一步。

  1. 用细节树检查大小和约束。

ListView对Details树的约束和大小。

展开ListView的第一个renderObject,它包含了绘制小组件的信息。

  1. 橙色的文字表示尺寸缺失--难怪ListView在应用中缺失。
  2. 在查看constraints属性后,注意到高度约束被列为无穷大。现在错误信息更有意义了。ListView是一个 "视口",在其滚动方向上被赋予了一个无约束--换句话说是无限的--高度。

约束是由父部件传递下来的。所以,这里是widget如何确定其约束的快照。

Column: 你想占多少高度就占多少高度 ListView: 好的,我会占用所有的空间。 Column: 哇,但那就像无穷大一样,伙计。

而且,widgets不知道该怎么办......大小无法确定,因为ListView要的是无限高,这在屏幕上无法描绘。

旁白。为什么Column不将其子代的高度限制在自己的高度内?

你可能会出现这样的情况:第一个子代占据了所有空间,迫使第二个子代的高度为0。而且你不会立即知道,因为不会抛出错误。

  1. 导航回你的代码并修复它。
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·看不见?

  1. 检查控制台的错误信息。 这次你没有收到任何错误信息。继续进行下一步。

  2. 打开 "布局资源管理器"。 导航回到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。

  1. 导航回你的代码,并修复它。
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(),
  ],
)

热重装。恭喜你,你完成了菜单应用!

小结

通过本教程,你学会了。

  • 约束是通过小组件树向下传递的。
  • ExpandedRowColumn的子节点提供约束。
  • Flutter Inspector是你处理布局问题时最好的朋友。

要了解更多,请查看flutter.dev上的理解约束

祝你调试愉快!

关于作者:凯蒂是密歇根大学的大四学生,学习计算机科学。她目前是Flutter开发者关系团队的实习生,帮助开发者学习和构建很棒的应用程序。要了解她的工作,请访问她的GitHubLinkedIn


通过( www.DeepL.com/Translator )(免费版)翻译