Flutter 中的弹性布局是 Flex。Flex 类似于 CSS 的 Flexbox。

Flex 有主轴和交叉轴,Flex 里的子 Widget 默认沿主轴排列,并且 Flex 和 Expanded 配合使用可以实现子 widget 按照一定比例来分配父容器空间。所以 Flex 叫做弹性布局。
Flutter 学习(二十一)弹性布局 - 图1

Flex 的快速上手

Flex 有一个必填参数:direction,用于确定主轴的方向,然后就可以在 children 里写子 Widget。

  1. Flex(
  2. direction: Axis.horizontal,
  3. children: <Widget>[
  4. ...
  5. ],
  6. )

Flex 的构造函数及参数说明

Flex 的构造函数为:

  1. class Flex extends MultiChildRenderObjectWidget {
  2. Flex({
  3. Key key,
  4. @required this.direction,
  5. this.mainAxisAlignment = MainAxisAlignment.start,
  6. this.mainAxisSize = MainAxisSize.max,
  7. this.crossAxisAlignment = CrossAxisAlignment.center,
  8. this.textDirection,
  9. this.verticalDirection = VerticalDirection.down,
  10. this.textBaseline,
  11. List<Widget> children = const <Widget>[],
  12. }) : assert(direction != null),
  13. assert(mainAxisAlignment != null),
  14. assert(mainAxisSize != null),
  15. assert(crossAxisAlignment != null),
  16. assert(verticalDirection != null),
  17. assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
  18. super(key: key, children: children);
  19. ...
  20. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
direction Axis 主轴的方向 必选
mainAxisAlignment MainAxisAlignment 表示子 Widget 在主轴的对齐方式 可选
mainAxisSize MainAxisSize 表示主轴应该占用多大的空间 可选
crossAxisAlignment CrossAxisAlignment 表示子 Widget 在交叉轴的对齐方式 可选
textDirection TextDirection 表示子 Widget 在主轴方向上的布局顺序 可选
verticalDirection VerticalDirection 表示子 Widget 在交叉轴方向上的布局顺序 可选
textBaseline TextBaseline 排列子 Widget 时使用哪个基线 可选
children List< Widget> Flex 布局里排列的内容 可选

direction : 主轴的方向

direction 的类型是 Axis:

Axis 的值 含义
Axis.horizontal 主轴方向为水平方向,那么子 Widget 就会沿水平方向排列,交叉轴就是垂直方向。
Axis.vertical 主轴方向为垂直方向,那么子 Widget 就会沿垂直方向排列,交叉轴就是水平方向。

Flutter 学习(二十一)弹性布局 - 图2

mainAxisAlignment :子 Widget 在主轴的对齐方式

mainAxisAlignment 的类型是 MainAxisAlignment:

MainAxisAlignment 的值 含义
MainAxisAlignment.start 沿着主轴的起点对齐
textDirection 必须有值,以确定是从左边开始的还是从右边开始的
MainAxisAlignment.end 沿着主轴的终点对齐
textDirection 必须有值,以确定是在左边结束的还是在右边结束的
MainAxisAlignment.center 在主轴上居中对齐
MainAxisAlignment.spaceBetween 在主轴上,两端对齐,项目之间的间隔都相等。
MainAxisAlignment.spaceAround 在主轴上,将多余的控件均匀分布给子 Widget 之间,而且第一个子 Widget 和 最后一个子 Widget 距边框的距离是两个子 Widget 距离的一半
MainAxisAlignment.spaceEvenly 在主轴上,将多余的控件均匀分布给子 Widget 之间,而且第一个子 Widget 和 最后一个子 Widget 距边框的距离和子 Widget 之间的距离一样

Flutter 学习(二十一)弹性布局 - 图3

mainAxisSize : 表示主轴应该占用多大的空间

MainAxisSize 的值 含义
MainAxisSize.min 主轴的大小是能显示完子 Widget 的最小大小,主轴的大小就是子 Widget 的大小
MainAxisSize.max 主轴能显示的最大的大小,根据约束来判断

紫色代表主轴占用的空间:

Flutter 学习(二十一)弹性布局 - 图4

crossAxisAlignment :表示子 Widget 在交叉轴的对齐方式

crossAxisAlignment 的类型是 CrossAxisAlignment:

CrossAxisAlignment 的值 含义
CrossAxisAlignment.start 沿着交叉轴的起点对齐
verticalDirection 必须有值,以确定是从左边开始的还是从右边开始的
CrossAxisAlignment.end 沿着主轴的终点对齐
verticalDirection 必须有值,以确定是在左边结束的还是在右边结束的
CrossAxisAlignment.center 在交叉轴上居中对齐
CrossAxisAlignment.stretch 要求 子Widget 在交叉轴上填满
CrossAxisAlignment.baseline 要求 子Widget 的基线在交叉轴上对齐

Flutter 学习(二十一)弹性布局 - 图5

textDirection :表示子 Widget 在主轴方向上的布局顺序

textDirection 的类型是 TextDirection:

TextDirection 的值 含义
TextDirection.rtl 表示从右到左
TextDirection.ltr 表示从左到右

Flutter 学习(二十一)弹性布局 - 图6

verticalDirection :表示子 Widget 在交叉轴方向上的布局顺序

verticalDirection 的类型是 VerticalDirection:

VerticalDirection 的值 含义
VerticalDirection.up 表示从下到上
VerticalDirection.down 表示从上到下

Flutter 学习(二十一)弹性布局 - 图7

Flexible 与 Expanded

如果当 Flex 里的内容过长,超过主轴的大小,例如如下的代码:

  1. Flex(
  2. direction: Axis.horizontal,
  3. mainAxisAlignment: MainAxisAlignment.start,
  4. children: <Widget>[
  5. Text('Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!')
  6. ],
  7. )

Flex 的主轴是水平方向,而 Text 里的内容太多,超过了屏幕的宽度,就会抛出 layout 错误:

  1. A RenderFlex overflowed by 267 pixels on the right.

界面上也会看到黑黄的条:

Flutter 学习(二十一)弹性布局 - 图8

为了避免子 Widget 在 Row、Column、Flex 中超界,就可以使用 Flexible 与 Expanded。Flexible 与 Expanded 可以让 Row、Column、Flex 的子 Widget 具有弹性能力。

比如上面的例子用 Flexible 或 Expended 来改写:

  1. Flexible(
  2. child: Text(
  3. 'Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!'),
  4. )

  1. Expended(
  2. child: Text(
  3. 'Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!'),
  4. )

运行的效果如下:

Flutter 学习(二十一)弹性布局 - 图9

用 Flexible 与 Expanded 来包子 Widget,当子 Widget 要超过主轴的大小时,会自动换行,但是 Flexible 与 Expanded 也有不同的地方,Expanded 是 Flexible 的子类。

Flexible 的构造函数为:

  1. class Flexible extends ParentDataWidget<Flex> {
  2. const Flexible({
  3. Key key,
  4. this.flex = 1,
  5. this.fit = FlexFit.loose,
  6. @required Widget child,
  7. }) : super(key: key, child: child);
  8. ...
  9. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
flex int 此 Widget 的弹性因子 可选
fit FlexFit 如何分配弹性 Widget 在可用空间里的大小 可选
child Widget 要显示的 Widget 必选

Expanded 的构造函数为:

  1. class Expanded extends Flexible {
  2. /// Creates a widget that expands a child of a [Row], [Column], or [Flex]
  3. /// expand to fill the available space in the main axis.
  4. const Expanded({
  5. Key key,
  6. int flex = 1,
  7. @required Widget child,
  8. }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
  9. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
flex int 此 Widget 的弹性因子 可选
child Widget 要显示的 Widget 必选

可以明显看到,Flexible 和 Expanded 的 fit 参数不同,Flexible 的 fit 是 FlexFit.loose,Expanded 的 fit 参数是 FlexFit.tight。所以,当还有剩余空间时,Expanded 会占满剩余的所有空间,而 Flexible 只会占用自身大小的空间。

这里举一个例子,当 Text 的内容不够长时:

  1. Flexible(
  2. child: Container(
  3. color: Colors.yellow,
  4. child: Text('使用 Flexible 来包裹子 Widget'),
  5. ),
  6. ),
  1. Expanded(
  2. child: Container(
  3. color: Colors.yellow,
  4. child: Text('使用 Expanded 来包裹子 Widget'),
  5. ),
  6. ),

效果如下:

Flutter 学习(二十一)弹性布局 - 图10

Flexible 会占用自身大小,而 Expanded 会占满全屏幕。

总结

Flexible 与 Expanded 可以让 Row、Column、Flex 的子 Widget 具有弹性能力,当子 Widget 要超过主轴的大小时,会自动换行,当还有剩余空间时,Expanded 会占满剩余的所有空间,而 Flexible 只会占用自身大小的空间。

Fexible 和 Expanded 的 flex 弹性系数

Fexible 和 Expanded 还有一个很重要的参数:flex,flex 为弹性系数,其布局过程如下:

  1. 如果 flex 为 0 或 null,则 child 是没有弹性的,称为非弹性子 Widget,非弹性子 Widget 的大小就是其本身的大小,不会被扩展去占用多余的空间。
  2. 如果 flex 大于 0,child 是有弹性的,称为弹性子 Widget,首先会计算出第一步所有 flex 为 0 或 null 的 子Widget 的大小,然后会会按照弹性子 Widget 的 flex 占 有弹性子 Widget 的 flex 总和的比例来分割主轴的空闲空间。

Flexible 的 flex 的使用

Flexible 的 flex 的使用方式就是使用 Flexible 嵌套这些 Widget,然后设置 flex 的值:

  1. Flexible(
  2. flex: 1,
  3. child: ...
  4. )

Demo 如下:

  1. Flex(
  2. direction: Axis.horizontal,
  3. mainAxisAlignment: MainAxisAlignment.start,
  4. children: <Widget>[
  5. Flexible(
  6. flex: 1,
  7. child: Container(
  8. height: 30.0,
  9. width: 30.0,
  10. color: Colors.yellow,
  11. ),
  12. ),
  13. Flexible(
  14. flex: 2,
  15. child: Container(
  16. height: 30.0,
  17. width: 30.0,
  18. color: Colors.green,
  19. ),
  20. ),
  21. Flexible(
  22. flex: 1,
  23. child: Container(
  24. height: 30.0,
  25. width: 30.0,
  26. color: Colors.blue,
  27. ),
  28. ),
  29. ],
  30. ),

使用 Flexible 包裹三个宽高都为 30 的色块,并设置 flex 为 1、2、1,效果如下:

Flutter 学习(二十一)弹性布局 - 图11

因为子 Widget 的宽度是固定的,所以 Flexible 只会占用本身的大小。

Expanded 的 flex 使用

Expanded 的 flex 的使用方式就是使用 Expanded 嵌套这些 Widget,然后设置 flex 的值:

  1. Expanded(
  2. flex: 1,
  3. child: ...
  4. )

Demo 代码如下:

  1. Flex(
  2. direction: Axis.horizontal,
  3. mainAxisAlignment: MainAxisAlignment.start,
  4. children: <Widget>[
  5. Expanded(
  6. flex: 1,
  7. child: Container(
  8. height: 30.0,
  9. width: 30.0,
  10. color: Colors.yellow,
  11. ),
  12. ),
  13. Expanded(
  14. flex: 2,
  15. child: Container(
  16. height: 30.0,
  17. width: 30.0,
  18. color: Colors.green,
  19. ),
  20. ),
  21. Expanded(
  22. flex: 1,
  23. child: Container(
  24. height: 30.0,
  25. width: 30.0,
  26. color: Colors.blue,
  27. ),
  28. ),
  29. ],
  30. ),

使用 Expanded 包裹三个宽高都为 30 的色块,并设置 flex 为 1、2、1,效果如下:

Flutter 学习(二十一)弹性布局 - 图12

虽然三个色块的宽度是固定的,但是 Expanded 还是按照比例瓜分了剩余的全部空间。

参考

【1】Flutter 实战
【2】Flutter 中文文档
【3】Flutter 完全手册