在阅读下面的文章之前,还请读者们先思考下面几个问题。
先来看看最简单的情况,如果Container没有Child,代码如下所示。
1. import 'package:flutter/material.dart';
2.
3. void main() => runApp(MaterialApp(
4. title: 'Travel',
5. home: HomePage(),
6. ));
7.
8. class HomePage extends StatelessWidget {
9. @override
10. Widget build(BuildContext context) {
11. return Scaffold(
12. appBar: AppBar(
13. title: Text('Container'),
14. ),
15. body: Container(
16. color: Colors.red,
17. ),
18. );
19. }
20.}
这种情况下,Container的展示如下图所示。
可以发现,Container会尽可能大的占据父布局的尺寸。
当Container有Child的时候,代码如下所示。
1. import 'package:flutter/material.dart';
2.
3. void main() => runApp(MaterialApp(
4. title: 'Travel',
5. home: HomePage(),
6. ));
7.
8. class HomePage extends StatelessWidget {
9. @override
10. Widget build(BuildContext context) {
11. return Scaffold(
12. appBar: AppBar(
13. title: Text('Container'),
14. ),
15. body: Container(
16. color: Colors.red,
17. child: Text('Text in Container'),
18. ),
19. );
20. }
21.}
这种情况下,展示效果如下图所示。
可以发现,Container使用了Child的尺寸。
Container可以给Child设置对齐方式,因此,在有Child的情况下,设置下Alignment,代码如下所示。
1. import 'package:flutter/material.dart';
2.
3. void main() => runApp(MaterialApp(
4. title: 'Travel',
5. home: HomePage(),
6. ));
7.
8. class HomePage extends StatelessWidget {
9. @override
10. Widget build(BuildContext context) {
11. return Scaffold(
12. appBar: AppBar(
13. title: Text('Container'),
14. ),
15. body: Container(
16. color: Colors.red,
17. child: Text('Text in Container'),
18. alignment: Alignment.center,
19. ),
20. );
21. }
22.}
这种情况下,Container的展示如下图所示。
可以发现,Container占据父布局的最大尺寸,并根据Alignment设置Child的显示位置。
前面的例子都是在Scaffold中设置的Container,那么如果直接在MaterialApp中设置Container呢?代码如下所示。
1. import 'package:flutter/material.dart';
2.
3. void main() => runApp(MaterialApp(
4. title: 'Travel',
5. home: HomePage(),
6. ));
7.
8. class HomePage extends StatelessWidget {
9. @override
10. Widget build(BuildContext context) {
11. return Container(
12. width: 300,
13. height: 300,
14. color: Colors.red,
15. );
16. }
17.}
在上面的代码中,Container被设置为固定宽高,如果在Scaffold中设置的Container,那么Container的宽高会被限定为具体的数值,但运行上面的代码,可以发现,在MaterialApp中,固定宽高的Container展示效果如下所示。
可以发现,设置的固定宽高并没有生效。
很奇怪是吗?下面继续给这个Container外面增加一个Center组件,代码如下所示。
1. import 'package:flutter/material.dart';
2.
3. void main() => runApp(MaterialApp(
4. title: 'Travel',
5. home: HomePage(),
6. ));
7.
8. class HomePage extends StatelessWidget {
9. @override
10. Widget build(BuildContext context) {
11. return Center(
12. child: Container(
13. width: 300,
14. height: 300,
15. color: Colors.red,
16. ),
17. );
18. }
19.}
同样是在MaterialApp中,同样是指定Container的固定宽高,这个时候,Container展示了正确的设置尺寸,展示效果如下图所示。
看完上面的例子,是不是发现原本以为完全掌握了的Container,尽然变得这么诡异?
其实可以总结下,Container的布局规则如下。
如果Container组件没有Child,也没有Alignment,那么Container会在给定的约束,例如指定宽高、Constraints的约束下,尽可能小的展示,其它情况下,会尽可能大的占据父布局空间。
Flutter的UI设计与其它语言一样,需要开发者对每个组件的布局行为烂熟于心,做到胸有成竹,这样才能在设计界面的时候,将设计稿完全转换为代码,如果不了解具体的布局行为,就会在布局时模棱两可,花费多余的时间进行调试和分析,所以,掌握Flutter的布局规则和行为,是学习Flutter组件非常重要的一步。
Flutter的渲染过程与AndroidView的渲染过程类似但又稍有不同,这里不详细讲解Flutter的渲染过程,只单独讲解下Flutter组件之间的Layout过程,也就是Flutter是如何确定一个组件的位置、尺寸等布局信息的。
Flutter的Widget也是一个树形结构,与Android View Tree类似,一个完整的布局过程分为下面几个步骤。
可以发现,Flutter的Layout过程也是一个深度优先遍历的过程,借助这一流程,回过头来看下上面提到的Container的布局行为。
首先,Container自身的布局约束为:最大尺寸为屏幕尺寸,当前尺寸为屏幕尺寸。
Container向下传递这个约束行为,而当Container没有Child的时候,就不会收到Child生成的新的布局约束,因此就使用了现有的布局约束,显示为屏幕宽高。
而当Container有Child的时候,Container收到了Child的尺寸约束,即Child自己的宽高,例如100x100,那么Container就会根据这一新的约束,修改自己的约束,将当前尺寸设置为Child的尺寸,这也就是为什么有Child的Container会展示出Child的尺寸的原因。
那么为什么MaterialApp和Scaffold的行为也不一样呢?很好理解,因为MaterialApp和Scaffold本身的约束设置就不一样,MaterialApp的Home Widget会被强制设置为屏幕的宽高,并作为一个固定尺寸。因此,在最后一个例子中,借助一个Align的组件,就可以完成Container的固定尺寸效果,原因是,此时Container已经不是MaterialApp的Child了,而Align组件本身就是会设置为父布局的最大尺寸,所以Container的宽高设置生效了。