Codelab: Flutter 布局基础教程

请注意,本文档已在官方文档中被移除,新的页面正在翻译中,本页面作为临时替代页面,将在新页面翻译结束之后删除,请勿在站外引用或分享以避免不好的用户体验,谢谢!

Row and Column 是 Flutter 世界非常重要的两个 widgets。想要把一个带 label 的 Text widget 放到另一个具有相应值的 Text widget边上?使用一个 Row。想要现实多对 labels 和值?使用一个包含多个 RowColumn。包含多个字段的表单,旁边有图标的菜单选项,旁边有搜索栏的按钮,这些都是要用到 Rows 和 Column 的地方。

codelab 将带你了解 Rows and Column 如何工作。因为它们非常相似,一旦你学会了如何使用 Row,codelab 将会向你展示如何把相同的概念应用于 Column。使用内置的编辑器,你将会边玩边测试你学到的知识。

从一个 Row 和一些 children开始

Row or Column 的主要功能就是包含其他的 widgets,这些 widgets 被称为 children。在一个 Row 里,所有的 children 都会根据 text 方向从头到尾水平排列。如果你的设备被设置为英文或其他从左到右的语言,就会从左开始,如果你使用阿拉伯语或者其他从右到左显示的语言,就会从右到左排列。

代码例子

下面是一个叫作 MyWidget 的 widget,在其内部创建了一个 Row,然后请试着将三个 BlueBox widgets 加到 Row 的 children中。

主轴空间

Row 的主轴是指水平方向的轴(Column 的主轴是指竖直方向的)。每一个 Row 都有一个叫 mainAxisSize 的属性,它决定了此 Row 沿着水平方向占用空间的大小。默认情况下,mainAxisSize 的值为 MainAxisSize.max,这意味着 Row 将会占用所有可用的水平方向空间。你可以使用 MainAxisSize.min 实现让一个 Row 占用尽可能少的空间。

代码例子

这里的例子是你刚刚完成的。试着将 RowmainAxisSize 的值设为 MainAxisSize.min,看看会发生什么。

主轴对齐

如果你将一个 RowmainAxisSize 设为最小值,在 children 之外就不会有更多的空间。如果你将其设为最大值,Row 就会有多出来的空间。你可以使用 mainAxisAlignment 属性来控制 Row 中的 children 对齐的方式。

MainAxisAlignment 有六种不同的枚举值:

  • 将所有的 children 尽可能向 Row 的 start 方向排列(如果是从左到右,那就是靠左排列)。

  • 将所有的 children 尽可能向 Row 的 end 方向排列。

  • 将 children 聚在 Row 主轴的中间位置。

  • 将主轴空白位置进行均分,用来在 children 之间制造间隔,首尾 children 距边缘没有间隙。

  • 很像 spaceBetween,除了让首尾 children 距边缘也有相同的间隙。

  • 很像 spaceEvenly,只是首尾 children 距边缘间距为中间 children 间距的一半。

代码例子

下面的 row 的 mainAxisAlignment 被设为了 start。试着将其改为其他的值,然后重新运行看看会怎么样。

交叉轴对齐

Row widgets 的交叉轴是竖直方向的轴,你可以用 crossAxisAlignment 属性来控制 children 如何在垂直方向排列。默认值是 CrossAxisAlignment.center,一共有五种值:

  • 将所有的 children 向 Row 竖直方向的 start 方向排列(如果是从上到下,你可以修改 verticalDirection 来改变)。

  • 将所有的 children 向 Row 竖直方向的 end 方向排列(默认是底部)。

  • 将 children 聚在 Row 竖直方向轴的中间位置。

  • 所有的 Children 的高度会被拉伸到和 Row 一样,填满竖直方向轴的空间。

  • 所有的 Children 的 baselines 在竖直方向对齐。

代码例子

Row 有两个小的 children 和一个大的。crossAxisAlignment 属性默认为 center。可以试着将其变为其他值然后重新运行,看看会怎样。

会有一个警告: CrossAxisAlignment.baseline requires that another property be set as well, so you will see an error if you try that one. 不用担心,在下一节将会对此进行讨论。

基线对齐

Sometimes it’s handy to align widgets containing text not by their overall bounds, but by the baselines used by their characters. That’s what CrossAxisAlignment.baseline is for. You can use it in combination with a Row’s textBaseline property (which indicates which baseline to use) to align a Row’s children along their baselines.

有时候根据包含文本的 widgets 的基线对齐是比较方便的,而不是根据它们的整体边框对齐。那就是 CrossAxisAlignment.baseline 的用途。你可以使用联合使用 RowtextBaseline属性(决定按照哪种基线来对齐),来决定 Row 的所有 children 根据基线对齐。

代码例子

row 里包含三个拥有不同字体大小的 Text widgets。试着将 crossAxisAlignment 属性设为 baseline,然后试验 textBaseline 的不同值(TextBaseline 枚举值里包含可用的 baseline 值)。

可伸缩 children

到目前为止,例子中所有用作 children 的 widgets 都有一个固定的大小。不过 Row 可以让它的 children 可伸缩,来适应可用的空间。为了更好的理解这是怎么回事儿,最好看看 Row 的大小和它的 children。

  1. 首先,Row 首先会要求它所有的 children 想要多大的尺寸。

  2. 然后,它会计算主轴(水平)的剩余空间。

  3. 然后它把剩下的空间根据 children 的 flex 值分给它的可伸缩的 children,这些可伸缩的 children 可以使用他们提供的部分或者全部的空间。

  4. 在那时, Row 知道所有的 children 的尺寸有多大,然后可以根据你之前学到的 axis size 和 alignment 属性来排列它们。

大多数 widgets 是固定大小的。你可以将他们包裹在一个 Flexible widget 中来将它们变为可伸缩的。 Flexibles 有两个重要属性: flex 值决定与其他 children 相比可占用剩余空间的多少, fit 属性决定其 child 是否占用所有额外的空间。

代码例子

试着将 row 中间的 box 包裹在一个 flex factor 为 1 并且 fitFlexFit.loose 的 Flexible widget中。然后试着将 fit 改为 FlexFit.tight`,看看会发生什么。

flex factor 为 1 和 fitFlexFit.tight 的组合是非常常见的, 更简单的方式是直接使用 Expanded widget.

Flex factors

如果 RowColumn 中多个 children 都是可伸缩的,那么如何分配可用空间取决于它们的 flex 值。每个 child 获得的空间将取决于他们的 flex 值占所有 children 的 flex 值之和的比例。

remainingSpace * (flex / totalOfAllFlexValues)

例如,如果有两个 flex 值为 1 的 children,每个将获得一半的可用空间。如果有两个 flex 值为 1 的 children,还有一个 flex 值为 2 的 child,那么前两个 children 将各获得四分之一的可用空间,另一个 child 将获得一半的可用空间。

代码例子

在这个例子中, Row 的所有三个 children 都是可伸缩的,试着改变它们的 flex 值然后重新运行看看它们的尺寸如何改变。

如果没有空间了怎么办?

正如你所看到的,当一个 Row 问它其中一个可伸缩的 child 想要多大空间时,它会根据这个 child 的 flex 值分配给它一个最大值。但是固定大小的 children 没有这个限制,它们可以自己决定大小。

一个副作用就是,无法阻止一个固定大小的 child 声明超出 Row 所能支持的大小。当这种情况发生时,就会发生溢出。你可以通过修改这个 child 的大小或者使用一个可滚动的 widget 来解决这个问题。

代码例子

下面的 Row 包含一个特别宽的 widget。运行代码看会发生什么,然后试着修改Container的宽度使其适应。

试着使用 SizedBox 来留出空间

如果你需要在一个 Row 中的两个 children 之间指定一个特定的间隔,一个简单的方法是在中间放一个宽度合适的 SizedBox

代码例子

试着用一个宽度 100 的 SizedBox 在两个 items 中间制造一些间隔。

Spacers 留出可变空间

使用Spacers 是另一个在 Row 的 children 之间留出空间的方法。它们是可伸缩的,可以填满任何剩下的空间。

代码例子

试着在第一个和第二个 children 之间加一个 Spacer

等等, 我不是还要学习 Columns 吗?

给你个惊喜,你已经学习了。Row 的所有用法和 Column 是一样的,只是维度不同。 Row 的主轴是水平的,而 Column 的主轴是竖直的,但是它们设置其 children 的大小和位置的方式是一样的。它们还共用一个基本类 Flex。所以你已经学习的有关 Row 的用法,同样适用于 Column

代码例子

这里有一个包含不同尺寸和一些重要属性已经设置好的 children 的 Column。试着摆弄以下,你会发现 Column 就像一个竖过来的的 Row

将它们放在一起

现在你已经熟悉了 RowColumn 的重要属性,你已经可以来联系将它门组合在一起来构建用户界面。下面的例子将带你完成一个名片显示的构建。

代码例子

每一张名片都需要一个名字和头衔,让我们从这里开始。

  • 添加一个 Column widget

  • 添加两个 text widgets 到 Column 的 children 列表中:

    • 第一个是名字(简短一点更适合于一个小窗口),使用 headline 样式:

style: Theme.of(context).textTheme.headline
  • 第二个 text widget 应该是 Experienced App Developer,使用默认样式(不用设置 style 属性)。

  • 设置 ColumncrossAxisAlignment 为 start,使得 text widgets 会开始对齐,而不是居中。

  • ColumnmainAxisSize 设为 MainAxisSize.min,这样 card 才不会扩展到整个 window 那么高。

名片的左上角通常会有一个图标或者标志,所以下一步是加一个到你的名片上。将你刚创建的 Column 包裹在一个 Row widget 中。

Row(
  children: [
    Column(  ), // <- This should be the Column you made in the previous step
  ],
);

现在你可以添加一个图标:

  • 在你的 RowColumn 的前面,加一个 Padding widget。

    • 设置 paddingconst EdgeInsets.all(8)

    • 将一个 Icon widget 作为 Padding widget 的 child。

      • 你可以使用任何 icon resource, Icons.account_circle看起来就不错。

      • Icon 的大小设置为 50。

你的第一个 Row 现在完成了。还有两件事要做,你需要一个 Column 把它们放进去。把你的 Row 包裹进一个 Column widget 就像这样:

 Column(
   children: [
     Row(  ), // <- This should be the Row with your Icon and Text widgets.
   ],
 );

然后按照以下步骤完成你的新 Column

  • 设置 Column 的 mainAxisSize 为最小

    • 否则它会充满整个屏幕!

  • 设置ColumncrossAxisAlignment 为 stretch。

    • 这使得所有 children 都会拉伸到最大宽度

  • Column 中你的 Row 下面添加更多的 widgets:

    • 一个高度为 8 的 SizedBox

    • 一个空 Row (没有 children 或其他属性)

    • 一个高度为 16 的 SizedBox

    • 另一个空 Row

现在就差几步了。接下来是第二个 Row。添加以下的 widgets 作为它的 children:

  • 一个地址为 ‘123 Main Street’ 的 Text widget

  • 一个电话为 ‘800-123-1234’ 的 Text widget

如果你现在运行代码,你会看到这两个 Textwidgets 是挨着的,而不是在 Row 的两端对齐,这是不对的。你可以将 RowmainAxisAlignment 设为 spaceBetween,使得这两个 Text widge 中间有些间隔。

最后一步是在名片的底部放一些图标。

  • 添加四个 Icon widgets 到最后一个 Row 中。你可以使用任何你喜欢的图标资源,但是以下图标是一个很好的选择,用来展示你想象中的关注于 accessibility, fast development, and multi-platform apps 的开发人员:

    • Icons.accessibility
    • Icons.timer
    • Icons.phone_android
    • Icons.phone_iphone
  • 设置 RowmainAxisAlignment 属性为 MainAxisAlignment.spaceAround