1.1.3 Container
在Flutter框架中,若需对一个组件进行包装或修饰,则最直接的办法就是将它嵌套在一个容器组件内。Container组件就是这样一个结合了定义尺寸、形状、背景颜色、间距、留白、装饰等多功能于一身的组件。虽然这些功能都有对应的组件,例如调节尺寸可以用SizedBox组件,设置间距可以用Padding组件,更改背景颜色可以用DecoratedBox组件,这些本书都会介绍到,但对于初学者而言,直接套用Container组件无疑是最简便且最容易记住的方式。对于有经验的开发者,适时使用Container组件也有助于在缩短代码量的同时增加代码的可读性。
除了包装和修饰其他组件之外,Container组件也可以直接用来绘制图形或色块,代码如下:
Container( width: 200, height: 100, color: Colors.grey, )
图1-4 用Container组件绘制一个矩形色块
以上代码利用width(宽度)、height(高度)和color(颜色)3个常用属性,定义了一个宽度为200逻辑像素、高度为100逻辑像素的灰色矩形,如图1-4所示。
1.嵌套子组件
除了绘制图形外,Container组件更常用于包装和修饰其他组件。开发者可借助其child参数传入另一个组件,以达成嵌套的效果。这里需要注意的是,如果嵌套的子组件的尺寸小于这个Container组件,则还需要通过传入alignment(对齐)属性设置内部的小组件应该怎样摆放,如居中、左上、右下等。如果没有传入alignment参数,则Container组件不会自动把child居中,而是会将child设置为自身尺寸。
例如上例中的灰色的Container组件可以使用child属性再嵌套一个黑色的Container组件,并在其中再次嵌套第3个白色的Container组件,并为它们分别设置对齐属性,代码如下:
运行效果如图1-5所示。
实战中,Container组件也常用于为其他组件设置背景颜色。例如,可将之前介绍的FlutterLogo组件尺寸设置为100单位(高度和宽度均为100逻辑像素),并嵌套在Container组件内,再用color属性把Container组件设置为黑色,代码如下:
Container( color: Colors.black, child: FlutterLogo(size: 100), )
运行效果如图1-6所示。
图1-5 用Container组件嵌套的形式修饰其他组件
图1-6 用Container组件使FlutterLogo的背景变成黑色
这里可以观察到FlutterLogo的背景变成了黑色(实际上是它的父级组件Container组件的填充色),并且由于FlutterLogo的尺寸是100单位,从视觉效果上,可以推测此时Container组件的尺寸似乎也应该是100单位。
实际上,这个Container组件的尺寸确实是100单位。在没有设置alignment属性的情况下,也没有刻意设置Container组件的高度和宽度时,它会尽量把自己的尺寸调节到child的尺寸,但若设置了alignment属性,它就不会这么做了。Container组件自身尺寸的算法相对复杂,有兴趣的读者可参考本节末尾的“Flutter框架小知识:Container组件的尺寸究竟是怎么确定的?”。
由此可见,如果直接将一个组件嵌套在Container组件中,而不设置Container组件的任何属性,视觉上就不会有效果,因为这个Container组件的尺寸会匹配child,且默认填充色为透明。
另外,Flutter框架中还有大量其他组件也有child属性,一般用于嵌套另一个组件,与上述用法一致,本书之后的章节将不再详细介绍其他组件的child属性。
2.常用属性
1)width和height
宽度和高度,类型为double小数,默认值为null。用于设置Container组件的尺寸,单位是逻辑像素。
Dart Tips语法小贴士
Dart空安全(null-safety)及遗留代码
上文提到Container组件尺寸属性的类型为double,默认值是null。考虑空安全,实际上Container组件的尺寸属性应是“double?”而不是double类,前者为“可空的小数”类型而后者为“不可空的小数”类型。在2021年3月3日发布的Flutter 2.0中,Flutter开始默认采用支持空安全的Dart 2.12版,因此组件的可选参数都必须是可空类型,即可选的color参数需写为“Color?”,可选的子组件应是“Widget?”类等。本书为了提高阅读流畅度,在提及Dart类型时尽量不添加问号,以免在阅读时引起不必要的断句障碍。
在2021年3月之前启动的Flutter项目可能还没有迁移至Flutter 2.0空安全,因此在那些项目代码中,任何类型都是可空的,如int、bool、double等,初始值都是null,而不是很多编程语言里常见的0、false或者0.0等。在启用空安全之前,只有赋值定义如inti=0时,其初始值才是0,否则直接定义inti就相当于inti=null,因此在旧代码中,若看到其他开发者写了if(checked==true)的时候,不要轻易地把它“简化”成if(checked),因为空安全之前的bool有3种状态,因此上段代码的原作者可能是指if(checked!=false&& checked!=null),所以一定要仔细检查确认后再决定。
如果一个Container组件只被设置了尺寸,而没有用到其他的功能,如填充色或修饰属性等,也可以考虑直接使用SizedBox组件,在本章后面小节会介绍。
2)color
填充色,类型为Color(可空颜色类,准确而言是“Color?”),当其值为null时则为透明,没有填充效果。通常在开发的过程中,为了看清Container组件的尺寸和位置,可以临时为它设置一些不透明或半透明的颜色,如Colors. blue蓝色或Colors. red. withOpacity(0.5)半透明的红色等,以便直观地观察和调整布局。
这里的填充色必须是单一的颜色,如需使用颜色渐变(有时也译作色彩梯度或颜色带),可以使用decoration(修饰)属性。需要注意的是,颜色和修饰属性有冲突,如果使用decoration属性,则这里的color属性必须为空(传入null或删掉不传)。
3)child
子组件,类型为Widget。如果不传入,且没有定义Container组件的尺寸,则Container组件会尽量占满父级组件的全部空间。例如,当父级组件也没有定义或约束尺寸时,它会占满整个屏幕。
Container组件只支持嵌套一个子组件,如果需要传入多个组件,则可考虑使用Column、Row或Stack组件。这些布局组件在实战中都比较常见,在本章后面小节也会依次介绍。
4)alignment
当子组件的尺寸小于Container组件时,开发者可利用alignment属性设置对齐方式。类型为Alignment类,构造函数为Alignment(double x,doubley),其中x和y分别对应横轴和纵轴方向的位置,取值范围为[-1.0,1.0]。横轴(x)方向-1.0表示最左边,1.0表示最右边,0.0则坐落于正中间。纵轴(y)方向-1.0表示最顶部,1.0表示最底部,例如从上到下1/8的位置就可以用-0.25表示。
Alignment类型对常见的对齐场景已内置命名构造函数,方便开发者直接使用,以增加代码可读性。例如,左上对齐既可以用Alignment(-1.0,-1.0)表示,也可以直接用Alignment. topLeft表示。
如果child属性为空,即没有子组件需要对齐,则alignment属性会被直接忽略。
5)margin和padding
间距留白和填充留白,分别指Container组件的“外部”和“内部”的空白部分。类型为EdgeInsets类,基本的构造函数有EdgeInsets. fromLTRB(doubleleft,doubletop,double right,doublebottom),即依次单独设置左、上、右、下这4个方向分别留白多少逻辑像素。如果4个数值相同,则可以使用EdgeInsets. all(doublevalue)同时设置4个方向的值。如果只需设置其中几个值,则可以使用EdgeInsets. only()方法,传入需要设置的方向,省略的方向则自动为0。
例如,可通过margin和padding属性修改一个Container组件的留白情况,代码如下:运行效果如图1-7所示。
图1-7 Container的间距留白和填充
3.不常用属性
1)constraints
布局约束,可以传入BoxConstraints类型,约束其child组件的最大尺寸和最小尺寸,同时Container组件自身的尺寸也可能会受影响。有关Flutter布局约束和原理,以及该属性的具体用法和示例,读者可参考第6章“进阶布局”中介绍ConstrainedBox组件的内容。
实战中,若只需使用约束属性,而不需要使用Container组件的其他属性,则可考虑直接使用ConstrainedBox组件。实际上,Container组件本身只是一个结合了各种其他组件于一身的“便利组件”,它最主要的作用就是当一段代码需要同时使用大量布局组件时可减少嵌套的层数。
2)decoration
修饰属性是一个相对复杂的属性,可以包括形状、阴影、边框、渐变色填充等的修饰。修饰的效果会渲染在子组件child后面,作为背景。具体用法与DecoratedBox组件的同名属性相同,读者可参考第14章“渲染与特效”中关于DecoratedBox组件的内容。
由于本节介绍的Container组件本质是一个结合了定义尺寸、约束、形状、背景颜色、间距、填充、变形等功能于一身的组件,因此若只需用到修饰属性,则可直接使用DecoratedBox组件。
3)foregroundDecoration
前景修饰,同样是一个修饰属性,可以做到和decoration属性同样的效果,唯一的区别是修饰的效果会渲染在子组件child的前面,即z轴方向会叠加在decoration(背景修饰)和child(子组件)的上面。如果使用同样尺寸且不透明的修饰,则它可能会完全遮挡住child及背景修饰的内容。
例如,可结合decoration和foregroundDecoration两种修饰参数一同使用,代码如下:
图1-8 Container组件的背景修饰与前景修饰
这段代码定义了渐变效果的背景方块,并添加了阴影效果。内部嵌入了白色的正方形Container作为子组件,最后利用前景修饰参数做出圆形半透明覆盖层,运行效果如图1-8所示。
4)clipBehavior
裁边行为,这是为2018年6月的一个Flutter框架改动引起的新概念(4)而新增的一个参数。该改动取消了默认渲染时的反锯齿(anti-aliasing),因为绝大部分情况下反锯齿并不会带来画质的提升,却会造成不必要的资源浪费。默认关闭反锯齿可以大幅增加Flutter渲染的性能,但因为Container组件可以通过decoration属性支持“斜”裁边(如三角形裁边),如果裁边后锯齿严重,可以通过clipBehavior 属性手动启用反锯齿。
除了Container组件外,不少其他支持裁剪效果的组件也都有clipBehavior属性,本书将不再单独介绍。关于锯齿现象及clipBehavior属性,可查阅第14章关于“裁剪边框”的内容。
5)transform
变形参数,可以接收一个4×4的矩阵,获得对三维物体的缩放、平移和旋转的描述,并在渲染Container时应用变形效果。三维物体的任意缩放、平移和旋转都可以通过一个4× 4矩阵完成,14.1.5节将做简单介绍很多其他的图形框架采用类似设计,因此矩阵的计算方法实际上并不属于Flutter框架的知识范畴。
例如利用一个适当的矩阵,可以对上例(图1-8)中的Container做出x轴放大至150%,y轴缩小至75%,并沿z轴方向旋转45°的变形效果。数学计算后得出,这样的变形需要以下矩阵:
图1-9 利用矩阵达到变形效果
运行效果如图1-9所示。
实战中,如果只需使用变形属性,则可以直接使用Transform组件。当Container组件的transform属性不为空时,它实际就在背后自动调用了Transform组件,因此读者也可参考第14章“渲染与特效”中关于Transform组件的介绍。
Flutter 框架小知识
Container组件的尺寸究竟是怎么确定的
简而言之,没有子组件(child属性为空)的Container会尽量占满父级组件的全部可用空间,而有子组件的Container会尽量将自己与子组件的尺寸匹配。
具体情况如下:
(1)在没有子组件(child:null)的情况下。
a. 首先综合父级约束及自身的width、height、constraints属性计算约束;
b. 如果最终计算出的约束有界,则尽量占满;
c. 如果最终计算出的约束无界,则尽量缩小。
(2)在有child子组件的情况下。
a. 首先将父级约束及自身的width、height、constraints约束传递给child;
b. 等child确定完尺寸后,Container尽量缩小,以便匹配child尺寸;
c. 但若提供了alignment属性,则尽量放大,为对齐child创造条件;
d. 又若某维度的约束无界,则依然只能尽量缩小,以便匹配child尺寸。
(3)如果Container组件周围存在留白,则最终尺寸也可能受到影响。其中margin会直接增加Container组件的尺寸,而padding及decoration属性(若设置了边框修饰),仅在Container组件试图匹配child尺寸时额外增加尺寸。
综合起来也可以这样理解:没有child的Container组件,越大越好,除非约束无界;有child的Container组件,匹配child,除非child要对齐。
举例说明:
(1)例如某无child的Container组件的父级组件是Column,垂直方向无界,水平方向有边界(屏幕宽度),则该Container组件会选择高度为0(无界时尽量缩小),宽度匹配屏幕宽度(有界时尽量占满)。由于高度为0,该Container组件的面积为0,肉眼不可见。
(2)假设上例中的Container组件新增了一个child组件,并提供了alignment属性要求居中,则Container组件在垂直方向匹配子组件的尺寸,在水平方向占满全部可用空间,再将子组件居中。
(3)若Container组件有子组件但未提供对齐方式,且各种尺寸属性皆为空,则Container组件会将父级组件的布局约束直接传达给子组件,当子组件确定尺寸后,Container组件再将自身尺寸设置为子组件尺寸。