Flutter 中的弹性布局是 Flex。Flex 类似于 CSS 的 Flexbox。
Flex 有主轴和交叉轴,Flex 里的子 Widget 默认沿主轴排列,并且 Flex 和 Expanded 配合使用可以实现子 widget 按照一定比例来分配父容器空间。所以 Flex 叫做弹性布局。
Flex 的快速上手
Flex 有一个必填参数:direction,用于确定主轴的方向,然后就可以在 children 里写子 Widget。
Flex(
direction: Axis.horizontal,
children: <Widget>[
...
],
)
Flex 的构造函数及参数说明
Flex 的构造函数为:
class Flex extends MultiChildRenderObjectWidget {
Flex({
Key key,
@required this.direction,
this.mainAxisAlignment = MainAxisAlignment.start,
this.mainAxisSize = MainAxisSize.max,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
this.textBaseline,
List<Widget> children = const <Widget>[],
}) : assert(direction != null),
assert(mainAxisAlignment != null),
assert(mainAxisSize != null),
assert(crossAxisAlignment != null),
assert(verticalDirection != null),
assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
super(key: key, children: children);
...
}
参数名字 | 参数类型 | 意义 | 必选 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 就会沿垂直方向排列,交叉轴就是水平方向。 |
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 之间的距离一样 |
mainAxisSize : 表示主轴应该占用多大的空间
MainAxisSize 的值 | 含义 |
---|---|
MainAxisSize.min | 主轴的大小是能显示完子 Widget 的最小大小,主轴的大小就是子 Widget 的大小 |
MainAxisSize.max | 主轴能显示的最大的大小,根据约束来判断 |
紫色代表主轴占用的空间:
crossAxisAlignment :表示子 Widget 在交叉轴的对齐方式
crossAxisAlignment 的类型是 CrossAxisAlignment:
CrossAxisAlignment 的值 | 含义 |
---|---|
CrossAxisAlignment.start | 沿着交叉轴的起点对齐 verticalDirection 必须有值,以确定是从左边开始的还是从右边开始的 |
CrossAxisAlignment.end | 沿着主轴的终点对齐 verticalDirection 必须有值,以确定是在左边结束的还是在右边结束的 |
CrossAxisAlignment.center | 在交叉轴上居中对齐 |
CrossAxisAlignment.stretch | 要求 子Widget 在交叉轴上填满 |
CrossAxisAlignment.baseline | 要求 子Widget 的基线在交叉轴上对齐 |
textDirection :表示子 Widget 在主轴方向上的布局顺序
textDirection 的类型是 TextDirection:
TextDirection 的值 | 含义 |
---|---|
TextDirection.rtl | 表示从右到左 |
TextDirection.ltr | 表示从左到右 |
verticalDirection :表示子 Widget 在交叉轴方向上的布局顺序
verticalDirection 的类型是 VerticalDirection:
VerticalDirection 的值 | 含义 |
---|---|
VerticalDirection.up | 表示从下到上 |
VerticalDirection.down | 表示从上到下 |
Flexible 与 Expanded
如果当 Flex 里的内容过长,超过主轴的大小,例如如下的代码:
Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text('Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!')
],
)
Flex 的主轴是水平方向,而 Text 里的内容太多,超过了屏幕的宽度,就会抛出 layout 错误:
A RenderFlex overflowed by 267 pixels on the right.
界面上也会看到黑黄的条:
为了避免子 Widget 在 Row、Column、Flex 中超界,就可以使用 Flexible 与 Expanded。Flexible 与 Expanded 可以让 Row、Column、Flex 的子 Widget 具有弹性能力。
比如上面的例子用 Flexible 或 Expended 来改写:
Flexible(
child: Text(
'Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!'),
)
或
Expended(
child: Text(
'Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!Hello Flutter!'),
)
运行的效果如下:
用 Flexible 与 Expanded 来包子 Widget,当子 Widget 要超过主轴的大小时,会自动换行,但是 Flexible 与 Expanded 也有不同的地方,Expanded 是 Flexible 的子类。
Flexible 的构造函数为:
class Flexible extends ParentDataWidget<Flex> {
const Flexible({
Key key,
this.flex = 1,
this.fit = FlexFit.loose,
@required Widget child,
}) : super(key: key, child: child);
...
}
参数名字 | 参数类型 | 意义 | 必选 or 可选 |
---|---|---|---|
key | Key | Widget 的标识 | 可选 |
flex | int | 此 Widget 的弹性因子 | 可选 |
fit | FlexFit | 如何分配弹性 Widget 在可用空间里的大小 | 可选 |
child | Widget | 要显示的 Widget | 必选 |
Expanded 的构造函数为:
class Expanded extends Flexible {
/// Creates a widget that expands a child of a [Row], [Column], or [Flex]
/// expand to fill the available space in the main axis.
const Expanded({
Key key,
int flex = 1,
@required Widget child,
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
参数名字 | 参数类型 | 意义 | 必选 or 可选 |
---|---|---|---|
key | Key | Widget 的标识 | 可选 |
flex | int | 此 Widget 的弹性因子 | 可选 |
child | Widget | 要显示的 Widget | 必选 |
可以明显看到,Flexible 和 Expanded 的 fit 参数不同,Flexible 的 fit 是 FlexFit.loose,Expanded 的 fit 参数是 FlexFit.tight。所以,当还有剩余空间时,Expanded 会占满剩余的所有空间,而 Flexible 只会占用自身大小的空间。
这里举一个例子,当 Text 的内容不够长时:
Flexible(
child: Container(
color: Colors.yellow,
child: Text('使用 Flexible 来包裹子 Widget'),
),
),
Expanded(
child: Container(
color: Colors.yellow,
child: Text('使用 Expanded 来包裹子 Widget'),
),
),
效果如下:
Flexible 会占用自身大小,而 Expanded 会占满全屏幕。
总结
Flexible 与 Expanded 可以让 Row、Column、Flex 的子 Widget 具有弹性能力,当子 Widget 要超过主轴的大小时,会自动换行,当还有剩余空间时,Expanded 会占满剩余的所有空间,而 Flexible 只会占用自身大小的空间。
Fexible 和 Expanded 的 flex 弹性系数
Fexible 和 Expanded 还有一个很重要的参数:flex
,flex 为弹性系数,其布局过程如下:
- 如果 flex 为 0 或 null,则 child 是没有弹性的,称为非弹性子 Widget,非弹性子 Widget 的大小就是其本身的大小,不会被扩展去占用多余的空间。
- 如果 flex 大于 0,child 是有弹性的,称为弹性子 Widget,首先会计算出第一步所有 flex 为 0 或 null 的 子Widget 的大小,然后会会按照弹性子 Widget 的 flex 占 有弹性子 Widget 的 flex 总和的比例来分割主轴的空闲空间。
Flexible 的 flex 的使用
Flexible 的 flex 的使用方式就是使用 Flexible 嵌套这些 Widget,然后设置 flex 的值:
Flexible(
flex: 1,
child: ...
)
Demo 如下:
Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Flexible(
flex: 1,
child: Container(
height: 30.0,
width: 30.0,
color: Colors.yellow,
),
),
Flexible(
flex: 2,
child: Container(
height: 30.0,
width: 30.0,
color: Colors.green,
),
),
Flexible(
flex: 1,
child: Container(
height: 30.0,
width: 30.0,
color: Colors.blue,
),
),
],
),
使用 Flexible 包裹三个宽高都为 30 的色块,并设置 flex 为 1、2、1,效果如下:
因为子 Widget 的宽度是固定的,所以 Flexible 只会占用本身的大小。
Expanded 的 flex 使用
Expanded 的 flex 的使用方式就是使用 Expanded 嵌套这些 Widget,然后设置 flex 的值:
Expanded(
flex: 1,
child: ...
)
Demo 代码如下:
Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 30.0,
width: 30.0,
color: Colors.yellow,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
width: 30.0,
color: Colors.green,
),
),
Expanded(
flex: 1,
child: Container(
height: 30.0,
width: 30.0,
color: Colors.blue,
),
),
],
),
使用 Expanded 包裹三个宽高都为 30 的色块,并设置 flex 为 1、2、1,效果如下:
虽然三个色块的宽度是固定的,但是 Expanded 还是按照比例瓜分了剩余的全部空间。
参考
【1】Flutter 实战
【2】Flutter 中文文档
【3】Flutter 完全手册