Flutter 有着丰富的布局控件库 .
Flutter 中的布局整体分为
- Single-child layout widget (单子布局部件)
- Multi-child layout widget (多子布局部件)
本篇我们将开始学习单子布局部件 .
类似于 Android 学习中有五大布局, 可是到实际开发中 , 用到的最多的只有线性布局、相对布局、帧布局. Flutter 布局单子部件官网学习文档列就有 18 种 , 在实际开发中未必能全用到 . 为了节省学习成本 , 各位童鞋可先重点掌握一二 , 其余浅尝辄止即可 . 先有个简单的了解认识 , 待后期有适合的应用场景时再深入挖掘 ?
Container(重点)
Container Widget 即容器 , 是一个常用的部件 . 官方对其定义如下 :
一个方便的小部件 , 它结合了常见的对小部件的绘制、定位和确定大小
一个 Container 首先有padding围绕着子部件 (图中深绿色部分) , 将宽高作为约束 . 然后 被额外的空白控件围绕, 叫做 margin.
其绘制顺序大致为:
先应用给定的变换 transform
然后绘制 decoration
再绘制子部件 child
最后绘制 foregroundDecoration
transform 是指对widget在原有基础上做一些类似旋转、平移之类的变换 . decoration 以及 foregroundDecoration 是部件的 背景/前景 ‘装饰’ , 比如绘制部件的边框,背景图片等都是 decoration 和 foregroundDecoration中设置的 .下面我们用一段代码来说明:
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 布局行为顺序:
- 遵从 aligment
- 根据 子部件 child 确认自身大小
- 遵从 width , height , constraints
- 扩展以填充父部件
- 尽可能的小
没有子部件的 Container 会尽可能的大 , 除非传入的约束是无界的 , 这种情况它会尽可能的小 . 有子部件的 Container 根据子部件来确定大小 , 通过构造函数传入的 width , height 还有约束会将其覆盖 .
当然这段话理解起来有点模糊 . 我们来说的具体点 .
以下为 Container 各种情况下的大小
1. 若其无子部件 , 无宽高 , 没有约束 . 父部件提供了无界的约束 . Container将尽可能的小 .
eg:
@override
Widget build(BuildContext context) {
return new UnconstrainedBox(
child: new Container(
color: Colors.blue,
)
);
}
|
此时 Container将小到不可见
2. 若其无子部件 , 无排列 , 但是有宽高或者约束 , Container 将会在给定约束及父部件的约束结合下尽可能的小.
eg:
@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:
@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:
@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:
@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:
@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
在内部对齐子部件的部件, 根据子部件大小决定自身大小
@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:
@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:
@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 的一半
终于讲到一半了. continue…
IntrinsicHeight
当高度不受限制时, 我们希望子部件保持一个合理的高度而不是去尝试无限扩张 , 这个时候我们可以用 IntrinsicHeight
,不过这个类消耗较多 , 不建议使用
IntrinsicWidth
当宽度不受限制时, 我们希望子部件保持一个合理的宽度而不是去尝试无限扩张 , 这个时候我们可以用 IntrinsicWidth
,不过这个类消耗较多 , 不建议使用
LimitedBox
当不受约束时限制大小
Offstage
在其中的子部件不会被绘制,也不会占用空间.当offstage
属性为false时将会渲染子部件
eg:
@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:
@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(重点)