Flutter 有着丰富的布局控件库 .

Flutter 中的布局整体分为

  • Single-child layout widget (单子布局部件)
  • Multi-child layout widget (多子布局部件)

本篇我们将开始学习单子布局部件 .

类似于 Android 学习中有五大布局, 可是到实际开发中 , 用到的最多的只有线性布局、相对布局、帧布局. Flutter 布局单子部件官网学习文档列就有 18 种 , 在实际开发中未必能全用到 . 为了节省学习成本 , 各位童鞋可先重点掌握一二 , 其余浅尝辄止即可 . 先有个简单的了解认识 , 待后期有适合的应用场景时再深入挖掘 😐

为了更好的阅读体验 , 请点击 阅读原文 :)

Container(重点)

Container Widget 即容器 , 是一个常用的部件 . 官方对其定义如下 :

一个方便的小部件 , 它结合了常见的对小部件的绘制、定位和确定大小

一个 Container 首先有padding围绕着子部件 (图中深绿色部分) , 将宽高作为约束 . 然后 container 被额外的空白控件围绕, 叫做 margin.

其绘制顺序大致为:

  1. 先应用给定的变换 transform
  2. 然后绘制 decoration
  3. 再绘制子部件 child
  4. 最后绘制 foregroundDecoration

transform 是指对widget在原有基础上做一些类似旋转、平移之类的变换 . decoration 以及 foregroundDecoration 是部件的 背景/前景 ‘装饰’ , 比如绘制部件的边框,背景图片等都是 decoration 和 foregroundDecoration中设置的 .下面我们用一段代码来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class DemoContainer extends StatelessWidget {

@override
Widget build(BuildContext context) {
var imgUrl = "https://ws1.sinaimg.cn/large/006tNc79gy1fpa5bvsqskj3044048mx5.jpg";
return new Container(
padding: const EdgeInsets.all(16.0), // 内边距
color: new Color(0xFFF2F2F2), // 背景色
alignment: Alignment.center, //子部件对齐方式
child: new Container( // 子部件
width: 400.0, //宽
height: 400.0, //高
// color 与 decoration 互斥 .如需设置decoration 和 color , 可在decoration中设置color
// color: Colors.blueGrey,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
decoration: new BoxDecoration(
color: Colors.blueGrey,
border: new Border.all(
color: Colors.blue,
width: 8.0,
),
image: new DecorationImage(image: new NetworkImage(imgUrl))
),
child: new Text('Halcyon',style: const TextStyle(color: Colors.blue,fontSize: 24.0),),
)

);
}
}

效果图 :

首先导航栏下面整个是一个内边距padding 16 的灰色(#F2F2F2)背景的Container , 其唯一子部件是居中对齐的 . 然后我们主要说明的就是这个子部件 , 同样是个Container . 这个子 Container 宽高均为400 , 也设置了内边距 padding 16 (不过图中没有表现出来) .其 decoration 设置为拥有四周宽度为8的蓝色边框 . 并且有一个表情包图片作为容器背景 , 由于没指定图片拉伸方式 , 此时图片以原始大小居中显示 . 同时这个容器拥有一个内容为 ‘Halcyon’ 的文本子控件居中 .

由于以上提及的 Container 绘制顺序 , Container是先绘制 decoration , 再child ,再绘制 foregroundDecoration , 如若我们将代码中的 decoration 换成 foregroundDecoration , ‘Halcyon’ Text文本就会被 ‘前景装饰’ 中设置的图片覆盖了, 不可见.

布局行为

布局行为这个类似 Android 里View的 MeasureSpec 问题 . 可以先简单过一遍 , 等真正使用时, 结合实际例子进行理解.

Container 布局行为顺序:

  1. 遵从 aligment
  2. 根据 子部件 child 确认自身大小
  3. 遵从 width , height , constraints
  4. 扩展以填充父部件
  5. 尽可能的小

没有子部件的 Container 会尽可能的大 , 除非传入的约束是无界的 , 这种情况它会尽可能的小 . 有子部件的 Container 根据子部件来确定大小 , 通过构造函数传入的 width , height 还有约束会将其覆盖 .

当然这段话理解起来有点模糊 . 我们来说的具体点 .

以下为 Container 各种情况下的大小

1. 若其无子部件 , 无宽高 , 没有约束 . 父部件提供了无界的约束 . Container将尽可能的小 .

eg:

1
2
3
4
5
6
7
8
@override
Widget build(BuildContext context) {
return new UnconstrainedBox(
child: new Container(
color: Colors.blue,
)
);
}

此时 Container将小到不可见

2. 若其无子部件 , 无排列 , 但是有宽高或者约束 , Container 将会在给定约束及父部件的约束结合下尽可能的小.

eg:

1
2
3
4
5
6
7
8
9
10
11
@override
Widget build(BuildContext context) {
return new ConstrainedBox(
constraints: new BoxConstraints(maxHeight: 50.0,minWidth: 200.0),
child: new Container(
width: 100.0,
height: 100.0,
color: Colors.blue,
)
);
}

此时 Container宽50高200

3. 若其无子部件 , 无宽高 , 没有约束 , 没有排列 , 但是父部件提供了有界约束 . Container将会扩展至适应父部件提供的约束.

eg:

1
2
3
4
5
6
7
8
9
@override
Widget build(BuildContext context) {
return new ConstrainedBox(
constraints: new BoxConstraints(maxHeight: 50.0,maxWidth: 50.0),
child: new Container(
color: Colors.blue,
)
);
}

此时 Container宽高50

4. 若其有 alignment , 还有父部件提供的无界约束 . Container会确认自身大小与子部件接近

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
@override
Widget build(BuildContext context) {
return new UnconstrainedBox(
child: new Container(
color: Colors.blue,
alignment: Alignment.center,
child: new Container(
width: 50.0,
height: 50.0,
)
)
);
}

此时 Container 与子Container同大小.

5. 若其有 alignment , 还有父部件提供的有界约束 . Container会尝试去扩展以适应父部件 , 然后按照对齐方式放置子部件.

6. 若其有子部件 , 但是无宽高 ,约束 以及对齐方式 , 那么 Container 将约束从父部件传递给子部件,并将自身大小与子部件匹配

Padding

可给子部件内嵌边距padding的部件

与 Container 容器设置 padding 属性无太大差别.

eg:

1
2
3
4
5
6
7
8
9
10
11
12
@override
Widget build(BuildContext context) {
return
new Container(
color: Colors.blueGrey,
child: new Padding(
padding: new EdgeInsets.all(16.0),
child: const Card(color: Colors.white, child: const Text('halcyon'),
),
),
);
}

Center

置子部件居中的部件

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@override
Widget build(BuildContext context) {
return new Container(
width: 300.0,
height: 300.0,
color: Colors.grey,
child: new Center(
widthFactor: 1.0,
// 设置Center Widget 的宽为child widget的宽度倍数 . eg: 1.0 代表同子控件大小
heightFactor: 1.0,
// 同上 , 作用于高
child: new Container(
child: const Text('Center Words'),
color: Colors.lightBlue,
),
),
);
}

Align

在内部对齐子部件的部件, 根据子部件大小决定自身大小

1
2
3
4
5
6
7
8
9
@override
Widget build(BuildContext context) {
return new Center(
child: new Align(
alignment: Alignment.centerRight,
child: const Text('Halcyon',style: const TextStyle(color: Colors.blue ,fontSize: 24.0),),
),
);
}

‘Halcyon’文本位于父部件中右方

FittedBox

根据 BoxFit 对子部件拉伸及定位

BoxFit.none

对齐目标盒子中的元素 (默认居中) , 然后丢弃盒外的元素.

图片元素不会被拉伸.

BoxFit.contain

尽可能地大,同时包含完整的目标盒子

BoxFit.cover

尽可能地小,但仍覆盖目标盒子

BoxFit.fill

比例拉伸以填充目标盒子

BoxFit.fitHeight

确保显示目标的全部高度,忽视横向是否显示完整

BoxFit.fitWidth

确保显示目标的全部宽度,忽视纵向是否显示完整

scaleDown

对其目标(默认居中),如果需要,则会缩放目标使其在盒子内

AspectRatio(重点)

尝试给子部件指定比例确认大小.

eg:

1
2
3
4
5
6
7
8
9
10
11
12
@override
Widget build(BuildContext context) {
return new Container(
color: Colors.blueGrey,
alignment: Alignment.center,
child: new AspectRatio(
aspectRatio: 3.0 / 1.0, // ratio = 宽 / 高 ,
child: new Container(
color: Colors.purple,
),)
);
}

代码中所示为 Container1 > AspectRatio > Container2 的布局层次 , AspectRatio布局指定了子布局宽高比属性 aspectRatio 为 3.0 / 1.0 , 又因为未指定部件大小 , 部件默认填充父部件 , 因此 Container2 宽为屏幕宽度 , 高为宽的 1/3 .

ConstraintedBox(重点)

用以给子部件添加额外约束. 比如可以给子部件添加一个最低高度50像素

eg:

1
2
3
4
5
6
7
8
9
10
11
12
@override
Widget build(BuildContext context) {
return new Container(
color: Colors.blueGrey,
alignment: Alignment.center,
child: new ConstrainedBox(
constraints: new BoxConstraints(minHeight: 100.0,maxHeight: 300.0,minWidth: 100.0,maxWidth: 300.0),
child: const Card(child: const Text('Halcyon Days',
style: const TextStyle(color: Colors.teal, fontSize: 24.0)),),
),
);
}

代码示例中 , 我们给予 Card部件添加了一个最小宽/高度100.0 , 最大宽/高度300.0 . 当 ‘ Halcyon Days ‘ 文本变化时,其宽高始终在 [100,300] 的中变化 .

Baseline

顾名思义 , 就是根据子部件基线进行定位.

FractionallySizedBox(重点)

百分比布局

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@override
Widget build(BuildContext context) {
return new Container(
color: Colors.blueGrey,
alignment: Alignment.center,
child: new FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
child: new Container(
color: Colors.teal,
),
),
);
}

代码示例中 Container1 > FractionallySizedBox > Container2 . Container2 宽高均为 Container1 的一半

IntrinsicHeight

当高度不受限制时, 我们希望子部件保持一个合理的高度而不是去尝试无限扩张 , 这个时候我们可以用 IntrinsicHeight ,不过这个类消耗较多 , 不建议使用

IntrinsicWidth

当宽度不受限制时, 我们希望子部件保持一个合理的宽度而不是去尝试无限扩张 , 这个时候我们可以用 IntrinsicWidth ,不过这个类消耗较多 , 不建议使用

LimitedBox

当不受约束时限制大小

Offstage

在其中的子部件不会被绘制,也不会占用空间.当offstage 属性为false时将会渲染子部件

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Container(
height: 200.0,
color: Colors.grey,
),
new Offstage(
offstage: true, // 默认为true ,
child: new Container(
height: 100.0,
color: Colors.pink,
),
),
new Container(
height: 100.0,
color: Colors.teal,
)
],
);
}

以上示例 , 当 offstage 为 true时(默认), Offstage部件及其子部件将不会绘制, 类似Android 中给View设置Visibility为View.GONE一样; 而 offstage 为 false时, 将会绘制

OverflowBox

OverflowBox会给子部件施加一个与其自身从父部件直接获取的不同的约束,可能会使得其溢出父部件

SizedBox

有着明确尺寸的盒模型

SizedOverflowBox

有着明确尺寸的盒模型,但是会传递原始约束给子部件,可能会溢出

Transform(重点)

Transform在绘制子部件之前将应用变换效果

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@override
Widget build(BuildContext context) {
return new Container(
color: Colors.grey,
alignment: Alignment.center,
child: new Transform(
alignment: Alignment.topRight,
transform: new Matrix4.rotationZ(50.0),
child: new Container(
padding: const EdgeInsets.all(8.0),
height: 200.0,
width: 200.0,
color: Colors.teal,
child: const Text('halcyon'),
),
),
);
}

CustomSingleChildLayout(重点)