从Container尺寸之谜看Flutter的渲染规则

Container是Flutter中的一个非常基础且重要的组件,而且是一个非常有用的胶水组件,它可以作为很多效果的过渡容器,掌握Container的使用对理解Flutter的布局是非常有帮助的。

在阅读下面的文章之前,还请读者们先思考下面几个问题。

  • 在不设置Container宽高的前提下,如果Container没有Child,那么它的尺寸是多少?
  • 在不设置Container宽高的前提下,如果Container有Child,那么它的尺寸又是多少?
  • 如果Container有Child,且设置了Alignment,那么它的尺寸又是多少?
  • 分别在MaterialApp和Scaffold中设置Container,尺寸展示会有不同吗?

Container没有Child

先来看看最简单的情况,如果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

当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并设置Alignment

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的显示位置。

在MaterialApp的Home属性中设置指定宽高的Container

前面的例子都是在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的布局规则

Flutter的UI设计与其它语言一样,需要开发者对每个组件的布局行为烂熟于心,做到胸有成竹,这样才能在设计界面的时候,将设计稿完全转换为代码,如果不了解具体的布局行为,就会在布局时模棱两可,花费多余的时间进行调试和分析,所以,掌握Flutter的布局规则和行为,是学习Flutter组件非常重要的一步。

Flutter的渲染过程与AndroidView的渲染过程类似但又稍有不同,这里不详细讲解Flutter的渲染过程,只单独讲解下Flutter组件之间的Layout过程,也就是Flutter是如何确定一个组件的位置、尺寸等布局信息的。

Flutter的Widget也是一个树形结构,与Android View Tree类似,一个完整的布局过程分为下面几个步骤。

  1. 根节点Widget向下分发自身的布局约束
  2. 根据收到的布局约束和自身布局约束,生成新的约束并继续向下分发
  3. 一直到树的叶子节点,Widget将生成的约束向上传递给父节点
  4. 根据收到的布局约束和自身布局约束,生成新的约束并继续向上传递

可以发现,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的宽高设置生效了。

原文发布于微信公众号 - Android群英传(android_heroes)

原文发表时间:2019-03-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券