Flutter布局总纲——向下传递束缚,向上传递尺度。
Box束缚
束缚是Flutter布局的中心,在Flutter中,束缚的表现形式是经过Constraints类来完成的,所有的非翻滚布局模型,都经过BoxConstraints来进行束缚,它的代码如下。
从上面的代码能够看出,束缚本质上便是「宽」「高」上的「最大」「最小」规模。
BoxConstraints具有传递性,束缚会在组件树上传递,当时Widget会受到来自父级的束缚,一起也会将束缚传递给它的子Widget。
经过一个比如,咱们来了解下束缚是怎么进行传递的。
void main() {
runApp(
Container(
color: Colors.cyan.shade200,
width: 10,
height: 10,
child: Center(
child: Container(
color: Colors.red.shade200,
width: 300,
height: 300,
child: FlutterLogo(size: 1000),
),
),
),
);
}
咱们先提出这样几个问题:
- 第一个Container的10×10能否收效
- 第二个Container的300×300能否收效
- FlutterLogo的1000×1000能否收效
运转结果如下。
从运转作用来看,第一个Container的尺度被无视了,第二个Container的尺度收效了,FlutterLogo的尺度也被无视了。那么为什么会这样呢?一图胜千言,跟着下面这张图的线路,咱们能够好好了解下束缚是怎么进行传递的。
在Flutter中,每个组件都有自己的布局行为:
- Root,传递紧束缚,即它的子元素,有必要是设备的尺度,不然Root底子不知道未被撑满的内容该怎么显现
- Container,在有Child的时分,传递紧束缚,即子元素有必要和它相同大,不然Container也不知道该怎么放置Child
- Center,将紧束缚转换为松束缚,Center能够将父级的紧束缚,变松,这样它的子元素能够挑选放置在居中的方位,而子元素详细有多大?只要不超越父容器巨细都能够
这便是Flutter布局的中心思维。
父容器一层层向下先传递束缚,即最大最小宽高,子元素根据父元素的束缚,修改自己的束缚,并继续向下传递,到根子节点之后,将根据束缚修正后得到的尺度,返回给父级,直到根节点。
这也是为什么有些元素设置的尺度,会被束缚吃掉的原因。在Flutter中,元素的尺度,在不同的父级组件下,会展示出不同的束缚作用,然后展示出不同的款式,这是和Android View十分不同的一点。
更多的示例,咱们能够参阅下面几篇文章。
Flutter布局综述
Flutter布局指南之深化了解BoxConstraints
Flutter布局指南之Box套盒子
调试束缚
不同组件的束缚行为不相同,咱们平时能够经过下面两种方法来调试,获取组件当时的束缚。
LayoutBuilder
首先咱们能够经过LayoutBuilder来打印当时Widget的束缚,示例代码如下。
runApp(
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print('Root constraints $constraints');
return Container(
color: Colors.cyan.shade200,
width: 10,
height: 10,
child: Center(
child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
print('Center constraints $constraints');
return Container(
color: Colors.red.shade200,
width: 300,
height: 300,
child: FlutterLogo(size: 1000),
);
}),
),
);
},
),
);
输出:
flutter: Root constraints BoxConstraints(w=390.0, h=844.0)
flutter: Center constraints BoxConstraints(0.0<=w<=390.0, 0.0<=h<=844.0)
凭借它,咱们就能够很便利的查到当时束缚详细是什么束缚,以及到底是多少束缚。但是这样做仍是有点费事,所以咱们能够凭借Flutter的调试工具。
Flutter Inspector
在Flutter Inspector中,咱们能够查看当时Widget Tree的束缚情况,在Layout Explorer中,能够看到束缚的详细数值,如下所示。
在Widget Detail Tree中,咱们还能够看到详细的BoxConstraints对象,如下所示。
这种方法比前面打log的方法更加直观便利。
束缚的松与紧
BoxConstraints界说了最大最新规模之后,还界说了两个语义名词——「松束缚」「紧束缚」。
- 松束缚:minWidth和minHeight都为0的束缚
- 紧束缚:minWidth和maxWidth持平,并且minHeight和maxHeight持平的束缚
松束缚和紧束缚并不是相对的,它们是能够一起存在的。
对于Child来说,它无法违法父级的布局束缚,就像下面这个比如。
void main() => runApp(
Container(
color: Colors.cyan.shade200,
width: 10,
height: 10,
child: FlutterLogo(size: 1000),
),
);
Container�虽然对Child施加了紧束缚,但由于Root对Container施加的也是紧束缚,所以Container的束缚失效了。
不同的Widget,在不同的场景下所发生的束缚是不相同的,对于Container来说:
- 有Child就挑选Child的尺度(有设置alignment时会将束缚放松)
- 没有Child就撑满父级空间(父级空间为Unbound时,尺度为0)
打破束缚束缚
由于父组件的紧束缚会强制子组件也施赶紧束缚,这种束缚在某些场景下不太灵敏,所以Flutter提供了UnconstrainedBox�来解除这种束缚,仍是上面的比如,咱们加上UnconstrainedBox�。
void main() => runApp(
UnconstrainedBox(
child: Container(
color: Colors.cyan.shade200,
width: 10,
height: 10,
child: FlutterLogo(size: 1000),
),
),
);
�这个时分,Container施加的新的紧束缚(10,10)就能够收效了。
除此之外,还有一些组件,例如——Align。
void main() => runApp(
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.cyan.shade200,
width: 100,
height: 100,
child: FlutterLogo(size: 1000),
),
),
);
�这些相似的组件,也会将父Widget的紧束缚放松为松束缚,从它们的运用方法上就能够了解它们的行为,由于如果不能去除紧束缚的话,相似对齐的需求就没有办法完成了。
Flex束缚
前面看的都是单个Child的容器布局,这类布局方法,咱们称之为Box布局,相对而言,相似Column和Row这样的布局方法,咱们称之为Flex布局。
Row本质上是direction: Axis.horizontal的Flex Widget,Column本质上是direction: vertical的Flex Widget。
在Column和Row中,有两类束缚组件,一种是清晰知道本身尺度的Widget,例如Text、Button,有束缚的Container等,还有一种是弹性组件,例如Expanded和Flexible等组件。
所以Column和Row在布局时,选用的是Flex束缚进行布局,布局依照下面的规则进行。
- 先依照unbound束缚,核算所有非Flex布局的组件的尺度
- 再对Flex组件进行布局,布局根据flex特点来分配剩下空间(Flex组件向下传递紧束缚)
上面的布局规则是针对主轴来说的,Flex的主轴束缚为unbound,Flex束缚在交叉轴上会设置为松束缚(如果crossAxisAlignment设置为stretch,那么会变成紧束缚)。
以Row为例,Row对child的束缚会修改为松束缚,然后不会束缚child在主轴方向上的尺度,所以当Row内的Child宽度大于屏幕宽度时,就会发生内容溢出的正告。
所以咱们通常会在Flex组件中运用Expanded组件来避免内容的溢出。Expanded组件会将主轴方向上的Child施赶紧束缚,然后避免溢出。咱们查看下Expanded的源码。
能够发现,Expanded其实便是Flexible�的封装,仅仅将fit设置为了FlexFit.tight�。所以,下面的代码是等价的。
Expanded(child: ColoredBox(color: Colors.yellow)),
Flexible(child: ColoredBox(color: Colors.cyan), fit: FlexFit.tight),
咱们再来看下面的这个比如。
Center(
child: SizedBox(
height: 100,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(width: 50, color: Colors.red),
Expanded(child: ColoredBox(color: Colors.yellow)),
Flexible(child: ColoredBox(color: Colors.cyan), fit: FlexFit.loose),
Container(width: 50, color: Colors.purple),
],
),
),
)
在上面的代码中,咱们得到了下面的结果。
如果把Flexible中的fit改为FlexFit.tight�,那么作用如下。
结合这个比如,咱们能够更好的了解Flex的布局模型(先核算非Flex Widget的尺度,再将剩下空间依照Flex进行拆分,所以不论fit怎么,其尺度是固定的)。
Expanded�组件便是根据FlexFit.tight的封装,它用于撑满剩下空间,FlexFit.loose虽然没有封装的组件,但它的运用也很常见,咱们能够很简单的完成Android束缚布局中的ConstraintedWidth这样的作用。
Container(width: 50, color: Colors.red),
Expanded(child: ColoredBox(color: Colors.yellow)),
Flexible(child: ColoredBox(color: Colors.cyan, child: Text('data')), fit: FlexFit.loose),
Container(width: 50, color: Colors.purple),
�
当内容变长时,会束缚其最大宽度,如下所示。
Wrap束缚
Wrap组件与Flex组件有些相似,但又有些不同,拿前面的比如来说,Row中的child组件如果超越了屏幕宽度,就会导致内容溢出,由于Flex组件其主轴上的束缚为unbound,而Wrap组件,其主轴上的束缚会被修改为松束缚,交叉轴上的束缚会被改为unbound,这样就能够完成流式的布局作用。
所以Wrap组件和Flex组件在本质上是相反的两种布局行为。
Stack布局束缚
Stack是一类比较特别的层叠组件,它的束缚方法和Column、Row相似,但又不完全相同,在Stack中,相同也分为两类组件,一类是Positioned组件,一类是非Positioned组件,然后Stack会依照下面的布局方法进行。
- 先依照非Positioned组件的尺度进行核算,将本身尺度设置为非Positioned组件的最大值
- 再对Positioned组件进行布局,依照方位束缚进行布局,但不能再改动Stack的尺度
特别场景下:如果悉数是Positioned组件,那么Stack将取得父容器的最大束缚,如果悉数是非Positioned组件,那么Stack将取得子元素的最大尺度
从束缚上来说,Stack相同会放松父布局的紧束缚,其行为和Align是相似的。
Stack的Fit特点
Stack有个Fit特点,需求特别注意,它能够设置为:
- StackFit.loose:向下传递送束缚(默许行为)
- StackFit.expand:向下传递紧束缚
- StackFit.passthrough:将父级的束缚向下传递
Stack设置Fit特点后,并不会对本身尺度有影响,它改动的是Child的尺度,经过修改束缚是紧束缚仍是松束缚,来影响Child的尺度,然后改动自己的尺度。所以Stack的Fit特点默许是loose,即松束缚。如果设置为expand,那么Stack将向Child传递一个紧束缚。
IntrinsicHeight与IntrinsicWidth
这两个组件在Flutter中的运用十分少,但在某些极点的场景下,却是十分有用的,它的主要功能,便是为了完成相似Android束缚布局中的Barrier的功能。
咱们以IntrinsicWidth为例,来看看它的作用。
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(height: 100, width: 50, color: Colors.red),
Container(height: 100, color: Colors.blue),
],
)
�这个布局作用如下所示。
由于蓝色的Container没有width束缚,所以它在交叉轴方向上的巨细是父布局最大尺度,这时分,咱们给它加上IntrinsicWidth�。
IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(height: 100, width: 50, color: Colors.red),
Container(height: 100, color: Colors.blue),
],
),
)
�这时分作用如下所示。
能够发现,蓝色Container被强制加上了赤色Container的尺度束缚,这便是IntrinsicWidth的作用——在宽度或者高度上施赶紧束缚来束缚Child的尺度,其束缚来自于Child的固有宽度或者高度。
凭借这个特性,咱们能够很便利的完成一些作用。
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
OutlinedButton(onPressed: null, child: Text('btn1')),
OutlinedButton(onPressed: null, child: Text('btn222')),
OutlinedButton(onPressed: null, child: Text('btn333333')),
],
)
�这个比如,展示了3个不同长度的Button,经过stretch�特点将其交叉轴的长度设置为父布局的最大尺度,经过添加IntrinsicWidth,咱们能够完成下面的作用。
IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
OutlinedButton(onPressed: null, child: Text('btn1')),
OutlinedButton(onPressed: null, child: Text('btn222')),
OutlinedButton(onPressed: null, child: Text('btn333333')),
],
),
)
�这样的作用如下所示。
再例如这个比如。
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(color: Colors.red, height: 50),
Container(color: Colors.blue, height: 50, child: Text('data' * 8)),
Container(width: 100, color: Colors.yellow, height: 50),
],
)
作用如下。
便是由于赤色Container没有宽度束缚,所以撑满了,咱们现在想让赤色Container跟从蓝色Container的宽度而变化,那就能够运用IntrinsicWidth。
更多「Flutter」「Android」「Kotlin」内容,请关注我的微信公众号——【群英传】