一、你是否有这样的疑惑?
- 位置、大小飘忽不定?
为什么我的Widget位置/大小跟我想象的不一样?一会儿在这,加个child就跑到另外一个地方,或父Widget突然变大或变小了?这TMD该怎么布局啊。
- 想改变一个Widget的大小,不知如何下手?
我是从iOS平台转过来的,之前用数字精确定位视图的位置,现在它不给我设置数字的机会。或者使用约束,方便的很…
- 面对一个页面无从下手?
出现以上问题,大概率是因为不明白flutter的布局原理和声明式编程范式。本系列文章
将从“Flutter 设计者的角度
”来解释“为什么Flutter的布局会是这样的
”。OK, let`s go。
二、住建部和开发商
举个例子:A县城为了响应“建设美丽家乡”政策,决定对县城进行改造。住建部画出一块1000亩的地作为科技板块
交给一个靠谱的开发商开发。为了防止项目烂尾,住建部提出了3点要求:
- 必须合理安排布局,不能浪费土地资源不能烂尾;
- 所有的建筑必须经过审批,不能擅自开发。
- 板块内建筑可以自由设计,不能脱离该板块
开发商深刻领悟了会议精神,并总结如下:
- 必须用完这1000亩,不能多,更不能少(小心diao脑袋)。
- 建筑师的设计总建筑面积只要小于 1000亩即可,空出来的地做绿化带
最终项目顺利完成,A县被评为最美家乡称号。
我们来分析一下这个例子中的三个角色:
住建部:给开发商传递的一项硬性的指标:面积必须是1000亩
;多了,少了都没法向上交代,对吧?
开发商:在给出的方案也必须是1000亩才能通过审批,多了少了都不行,否则就拿不到这个项目了。
设计师:拿到的指标是面积小于等于1000亩,在1000亩以内,他可以自由发挥,因此他的方案只要在这个范围之内就可以。
这个例子中,住建部->开发商 ->设计师 提出要求,本质是上层对下层传递一种约束条件,设计师->开发商->住建部提交方案审核是下层向上层传递的是具体的方案,正是因为这种向下,向上的沟通方式,项目才得以完成。
flutter布局是就是的这个方式。
Flutter不希望开发者开发出来的页面是有手机屏幕的一半,也不能超出屏幕让用户看不到。当然相信你也不会像让你的app像下面图示一样:
因此,Flutter规定“App的页面必须占满整个屏幕
”。记住了,是“必须
”,没有一丝的商量余地。App页面里面的内容,只要不超过屏幕大小就可以了,大小你自己定
。
假设现在我们有一部手机,屏幕尺寸是320 * 480,Flutter给页面的约束就是:width = 320, height = 480
,页面返回的大小肯定比必须是320 * 480。(不然就强制拉伸或报错)
如果要在页面中中间布局一张20 x 20的图片,页面给他的子部件传递的约束通常是是:0 < width <= 320, 0 < height < 480
,然后图片会将位置信息:(x,y, width, height)交给上一级审批,然后由上级把它放在合适的位置。
**
这个过程也就是Flutter中的布局原理“向下传递约束,向上传递大小”。
**
二、代码演示
1、页面的约束
我们直接在RunApp中布局一个100 * 100 的 Container
,看下他的约束是什么。通过LayoutBuilder
可以查看约束。
void main() {
runApp(
LayoutBuilder(builder: (context, constraints){
/// 通过LayoutBuilder 查看约束
print(constraints);
return Container(
width: 100,
height: 100,
color: Colors.red,
);
})
);
}
输出信息如下:
... //省略无关信息
flutter: BoxConstraints(w=430.0, h=932.0)
... //省略无关信息
可以看到Flutter给我们的约束是:w=430.0, h=932.0。翻译过来就是宽度必须是430, 高度必须是932
。看一下实际效果:
可以看到我们设置的宽度=100和高度=100违反了上级的约束,所以不会生效。像这种width=xx,height=xx的约束,称为强约束
因为它不可违背(结合开头的例子:住建部的规定,不能违背)。
2、子部件的约束
接下来我们在Container内部放一个50 x 50的FlutterLogo
,看看什么效果。
void main() {
runApp(LayoutBuilder(builder: (context, constraints) {
/// 通过LayoutBuilder 查看约束
print('页面约束$constraints');
return Container(
alignment: Alignment.center,
color: Colors.red,
child: LayoutBuilder(
builder: (context, constraints) {
print('子部件约束$constraints');
return FlutterLogo(size: 50,);
},
),
);
}));
}
打印信息如下:
... //省略无关信息
flutter: 页面约束BoxConstraints(w=430.0, h=932.0)
flutter: 子部件约束BoxConstraints(0.0<=w<=430.0, 0.0<=h<=932.0)
... //省略无关信息
可以看到子部件获得的约束是0.0<=w<=430.0, 0.0<=h<=932.0
,是一个范围,而不是一个具体的值。那我们的FlutterLogo大小是满足这个约束的,因此FlutterLogo的大小应该是50x50。我们来看下效果:
像这种0.0<=w<=430.0, 0.0<=h<=932.0
宽高是一个范围的约束成为松约束
如果打破约束,我们将宽高设为1000x1000会怎样?代码改为 return FlutterLogo(size: 1000,);
查看一下它的尺寸:实际上是430x932,也就是说“约束不可被打破
”(除非强制打破约束,后续会讲)。
再来捋一下它的流程:父部件Container向子部件FlutterLogo传递约束0.0<=w<=430.0, 0.0<=h<=932.0,子部件根据约束布局了1000x100的大小告诉了父部件,父部件发现超过了约束,自动修正为最大尺寸。(当然如果超过了最小值,也会纠正)
三、Container的大小如何确定
首先我们要了解设计Container的初衷是什么?Container翻译过来时容器,容器是为了包裹住一些东西。我们来思考2个问题:
1、容器里什么都不放:容器应高设置为符合约束的最大值还是最小值?如果设置为最小值0,那么我们是不是看不见了,如果是最大值,我们是能看到的。
2、容器里放了一个子部件,容器应该是取子部件的最大值还是最小值?如果是最大值,我们写代码的时候是不是要每个容器都要加一堆这是大小的代码? 如果是最小值,会不会更好?
这里先告诉大家答案。
1、没有子部件时:Container会取约束的最大值;
2、有子部件是:Container会取约束的最小值;
其实也好理解:无子部件时,取最大值能让开发者看到效果,有子部件时,减少了控制大小的代码量,主要是为了方便。换句话说“也可以设置成其他值,只不过这样方便”。
验证代码如下:
(1)有子部件情况
void main() {
runApp(LayoutBuilder(builder: (context, constraints) {
/// 通过LayoutBuilder 查看约束
print('页面约束$constraints');
return Container(
alignment: Alignment.center,
color: Colors.red,
child: LayoutBuilder(
builder: (context, constraints) {
print('子部件约束$constraints');
return Container(
color: Colors.green,
child: FlutterLogo(size: 100,)
);
},
),
);
}));
}
效果:
(2)无子部件情况
void main() {
runApp(LayoutBuilder(builder: (context, constraints) {
/// 通过LayoutBuilder 查看约束
print('页面约束$constraints');
return Container(
alignment: Alignment.center,
color: Colors.red,
child: LayoutBuilder(
builder: (context, constraints) {
print('子部件约束$constraints');
return Container(
color: Colors.green,
// child: FlutterLogo(size: 100,)
);
},
),
);
}));
}
效果如下:
四、总结
1、Flutter中布局原理用一句话概括为“向下传递约束,向上传递大小”。
2、约束氛围紧约束和松约束:width = xx, height = xxx的是紧约束, xx < width < yy, xx < height < yy 的是松约束。
3、Container布局分为有子部件和无子部件情况:
有子部件时,取子部件的大小,无子部件时,取符合约束的最大值。
下期预告 :通过CustomMultiChildLayout 演示布局原理&自定义布局。
最后欢迎大家留言,一起交流,一起成长。