布局
鉴于 Flutter 是一个 UI 工具包,您将花费大量时间使用 Flutter 小部件创建布局。在本节中,您将学习如何使用一些最常用的布局小部件构建布局。您将使用 Flutter DevTools(也称为 Dart DevTools)来了解 Flutter 如何创建您的布局。最后,您将遇到并调试 Flutter 最常见的布局错误之一,令人恐惧的“无界约束”错误。
理解 Flutter 中的布局
#Flutter 布局机制的核心是小部件。在 Flutter 中,几乎所有东西都是小部件——甚至布局模型也是小部件。您在 Flutter 应用中看到的图像、图标和文本都是小部件。您看不到的东西也是小部件,例如排列、约束和对齐可见小部件的行、列和网格。
您可以通过组合小部件来构建更复杂的小部件从而创建布局。例如,下图显示了 3 个图标,每个图标下面都有一个标签,以及相应的小部件树:
在这个示例中,有一行包含 3 列,每列包含一个图标和一个标签。所有布局,无论多么复杂,都是通过组合这些布局小部件创建的。
约束
#理解 Flutter 中的约束是理解 Flutter 中布局工作方式的重要组成部分。
一般来说,布局是指小部件的大小及其在屏幕上的位置。任何给定小部件的大小和位置都受其父级约束;它不能具有任何它想要的大小,并且它不决定自己在屏幕上的位置。相反,大小和位置是由小部件与其父级之间的对话决定的。
在最简单的示例中,布局对话如下所示:
- 小部件从其父级接收其约束。
- 约束只是一组 4 个双精度数:最小和最大宽度,以及最小和最大高度。
- 小部件确定在这些约束内应为多大,并将它的宽度和高度传回父级。
- 父级查看它想要的大小以及它应该如何对齐,并相应地设置小部件的位置。对齐可以显式设置,使用各种小部件,如
Center
,以及Row
和Column
上的对齐属性。
在 Flutter 中,这种布局对话通常用简化的短语表达,“约束向下传递。大小向上传递。父级设置位置。”
盒子类型
#在 Flutter 中,小部件由其底层 RenderBox
对象呈现。这些对象确定如何处理传递给它们的约束。
一般来说,有三种盒子:
- 那些试图尽可能大的盒子。例如,
Center
和ListView
使用的盒子。 - 那些试图与其子元素大小相同的盒子。例如,
Transform
和Opacity
使用的盒子。 - 那些试图具有特定大小的盒子。例如,
Image
和Text
使用的盒子。
某些小部件,例如 Container
,会根据其构造函数参数的类型而有所不同。Container
构造函数默认为尝试尽可能大,但如果您给它一个宽度,例如,它会尝试遵守该宽度并具有该特定大小。
其他小部件,例如 Row
和 Column
(弹性盒子)根据给定的约束而变化。在理解约束文章中阅读更多关于弹性盒子和约束的信息。
布置单个小部件
#要在 Flutter 中布置单个小部件,请使用可以更改其在屏幕上位置的小部件(例如 Center
小部件)包装可见小部件(例如 Text
或 Image
)。
Widget build(BuildContext context) {
return Center(
child: BorderedImage(),
);
}
下图显示了一个未在左侧对齐的小部件,以及一个已在右侧居中的小部件。
所有布局小部件都具有以下任一项:
- 如果它们接受单个子元素,则具有
child
属性——例如,Center
、Container
或Padding
。 - 如果它们接受小部件列表,则具有
children
属性——例如,Row
、Column
、ListView
或Stack
。
Container
#Container
是一个便利的小部件,它由几个负责布局、绘制、定位和大小调整的小部件组成。关于布局,它可以用来向小部件添加填充和边距。还有一个 Padding
小部件可以在这里达到同样的效果。下面的示例使用 Container
。
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.0),
child: BorderedImage(),
);
}
下图显示了一个左侧没有填充的小部件,以及一个右侧有填充的小部件。
要在 Flutter 中创建更复杂的布局,您可以组合多个小部件。例如,您可以组合 Container
和 Center
:
Widget build(BuildContext context) {
return Center(
Container(
padding: EdgeInsets.all(16.0),
child: BorderedImage(),
),
);
}
垂直或水平布局多个小部件
#最常见的布局模式之一是垂直或水平排列小部件。您可以使用 Row
小部件水平排列小部件,使用 Column
小部件垂直排列小部件。本页上的第一个图同时使用了两者。
这是使用 Row
小部件的最基本示例。
Row
或 Column
的每个子元素本身都可以是行和列,组合起来构成一个复杂的布局。例如,您可以使用列向上面示例中的每个图像添加标签。
对齐行和列中的小部件
#在下面的示例中,每个小部件的宽度均为 200 像素,视口的宽度为 700 像素。因此,小部件一个接一个地左对齐,右侧的所有额外空间。
您可以使用 mainAxisAlignment
和 crossAxisAlignment
属性来控制行或列如何对齐其子元素。对于行,主轴水平运行,交叉轴垂直运行。对于列,主轴垂直运行,交叉轴水平运行。
将主轴对齐方式设置为 spaceEvenly
将空闲水平空间平均分配到每个图像之间、之前和之后。
列的工作方式与行相同。下面的示例显示了一个包含 3 个图像的列,每个图像的高度为 100 像素。渲染框(在本例中为整个屏幕)的高度超过 300 像素,因此将主轴对齐方式设置为 spaceEvenly
将空闲垂直空间平均分配到每个图像之间、上方和下方。
MainAxisAlignment
和 CrossAxisAlignment
枚举提供了各种常量用于控制对齐方式。
Flutter 包含其他可用于对齐的小部件,特别是 Align
小部件。
行和列中小部件的大小调整
#当布局过大而无法适应设备时,黄黑条纹图案会出现在受影响的边缘。在这个例子中,视口的宽度为 400 像素,每个子元素的宽度为 150 像素。
可以通过使用 Expanded
小部件来调整小部件的大小以适应行或列。要修复前面图像行过宽而无法适应其渲染框的示例,请使用 Expanded
小部件包装每个图像。
Expanded
小部件还可以指示小部件相对于其同级应该占据多少空间。例如,也许您希望一个小部件占据其同级两倍的空间。为此,请使用 Expanded
小部件的 flex
属性,这是一个确定小部件弹性因子的整数。默认弹性因子为 1。以下代码将中间图像的弹性因子设置为 2:
DevTools 和布局调试
#在某些情况下,盒子的约束是无界的或无限的。这意味着最大宽度或最大高度设置为 double.infinity
。当给定无界约束时,试图尽可能大的盒子将无法正常工作,并且在调试模式下会抛出异常。
渲染框最终具有无界约束的最常见情况是在弹性盒子(Row
或 Column
)内,以及在可滚动区域内(例如 ListView
和其他 ScrollView
子类)。例如,ListView
试图扩展以适应其交叉方向中可用的空间(也许它是一个垂直滚动的块,并试图与其父级一样宽)。如果您将垂直滚动的 ListView
嵌套在水平滚动的 ListView
内,则内部列表试图尽可能宽,这无限宽,因为外部列表在该方向上是可滚动的。
在构建 Flutter 应用程序时,您可能会遇到的最常见错误是由于不正确地使用布局小部件造成的,这被称为“无界约束”错误。
如果您在开始构建 Flutter 应用时只应准备好应对一种类型错误,那就是这个。
滚动小部件
#Flutter 具有许多内置的小部件,这些小部件会自动滚动,并且还提供各种您可以自定义的小部件来创建特定的滚动行为。在本页上,您将看到如何使用最常见的小部件来使任何页面都可滚动,以及用于创建可滚动列表的小部件。
ListView
#ListView
是一种类似列的小部件,当其内容长于其渲染框时,它会自动提供滚动。使用 ListView
的最基本方法与使用 Column
或 Row
非常相似。与列或行不同,ListView
要求其子元素占据交叉轴上的所有可用空间,如下面的示例所示。
当您有未知或非常多(或无限)数量的列表项时,通常会使用 ListView
。在这种情况下,最好使用 ListView.builder
构造函数。builder 构造函数仅构建当前在屏幕上可见的子元素。
在下面的示例中,ListView
显示一个待办事项列表。待办事项正在从存储库中获取,因此待办事项的数量是未知的。
自适应布局
#因为 Flutter 用于创建移动、平板电脑、桌面和 Web 应用,所以您可能需要根据屏幕大小或输入设备等因素调整应用程序的行为。这被称为使应用 自适应 和 响应式。
在创建自适应布局时,最有用的小部件之一是 LayoutBuilder
小部件。LayoutBuilder
是 Flutter 中许多使用“构建器”模式的小部件之一。
构建器模式
#在 Flutter 中,您会发现几个小部件在其名称或构造函数中使用“builder”一词。以下列表并非详尽无遗:
这些不同的“构建器”对于解决不同的问题很有用。例如,ListView.builder
构造函数主要用于延迟渲染列表中的项目,而 Builder
小部件对于在深度小部件代码中访问 BuildContext
很有用。
尽管它们的用例不同,但这些构建器的运作方式是统一的。构建器小部件和构建器构造函数都具有名为“builder”(或类似名称,例如 ListView.builder
的情况下的 itemBuilder
)的参数,并且 builder 参数始终接受回调。此回调是一个 构建器函数。构建器函数是将数据传递给父小部件的回调,父小部件使用这些参数来构建和返回子小部件。构建器函数总是至少传入一个参数——构建上下文——并且通常至少还有另一个参数。
例如,LayoutBuilder
小部件用于根据视口的大小创建响应式布局。构建器回调主体会收到它从父级接收的 BoxConstraints
,以及小部件的“BuildContext”。使用这些约束,您可以根据可用空间返回不同的部件。
在下面的示例中,LayoutBuilder
返回的小部件会根据视口是否小于或等于 600 像素或大于 600 像素而发生变化。
同时,ListView.builder
构造函数上的 itemBuilder
回调会传入构建上下文和一个 int
。此回调会为列表中的每个项目调用一次,而 int 参数表示列表项的索引。当 Flutter 正在构建 UI 时第一次调用 itemBuilder 回调时,传递给函数的 int 为 0,第二次为 1,依此类推。
这允许您根据索引提供特定配置。回想一下上面使用 ListView.builder
构造函数的示例:
final List<ToDo> items = Repository.fetchTodos();
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, idx) {
var item = items[idx];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(item.description),
Text(item.isComplete),
],
),
);
},
);
}
此示例代码使用传递到构建器中的索引从项目列表中获取正确的待办事项,然后在从构建器返回的小部件中显示该待办事项的数据。
为了举例说明这一点,以下示例更改了每个其他列表项的背景颜色。
其他资源
#- 常用布局小部件和概念
- 小部件的大小和位置
- 可滚动小部件
- 示例代码:使用长列表
- 示例代码:创建水平列表
- 示例代码:创建网格列表
- 视频:ListView—Flutter 每周小部件
- 自适应应用
API 参考
#以下资源解释各个 API。
反馈
#由于本网站的此部分正在不断发展,我们欢迎您的反馈!
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。