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