一文彻底理解Flutter布局约束
Posted 牧羊人.阿标
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文彻底理解Flutter布局约束相关的知识,希望对你有一定的参考价值。
前言
我们在写Flutter UI的时候,经常碰到明明widget设置width或者height,但是看上去就是无效呢。明明没有设置某个widget的大小,反而却呈现出我们想要的大小。又或者是各种莫名奇妙的overflow warning
其实要搞清楚这些问题的原因,我们必须要了解Flutter的布局约束。
文章目录
约束布局规则
了解Flutter布局约束之前,我们必须要先了解它的规则:
- 上层widget向下层widget传递约束条件
- 下层widget想上层widget传递大小信息
- 上册widget决定下层widget位置
如果我们在开发中不能熟练的运用这条规则,那么在布局的时候就会遇到各种麻烦,终究不能理解其原理。
细节描述:
- Widget会通过他的父级获得自身的约束。约束实际上就是四个浮点类型的集合:最大、最小宽度,最大、最小高度。
- 然后,这个widget会逐渐遍历他的children列表。向子级传递约束信息(子级之间的约束可能会有所不同),然后询问它的每个子级需要用于布局的大小。
- 然后这个widget就会对它的子级的children逐个进行布局。
- 最后,widget将会把它的大小信息向上传递至父widget(包括其原始约束条件)。
重要的限制
正如上述所介绍的布局规则所说的那样,Flutter的布局引擎有一些重要的限制:
- 一个widget仅仅在其父级给的约束的情况下才能决定自身的大小。这意味着widget通常情况下不能任意获得其想要的大小。
- 一个widget无法知道,也不需要决定其在屏幕中的位置。因为它的位置由其父级决定的。
- 当轮到父级决定其大小和位置的时候,同样的也取决于它自身的父级。所以,在不考虑整棵树的情况下,几乎不可能精确确定任何widget的大小和位置
- 如果子级想要拥有和父级不同的大小,然而父级没有足够的空间对齐进行布局的话,子级的设置的大小可能会不生效。这时候需要明确指定它的对齐方式
样例解释
所有样例代码都是比较简单,主要就是为了解释场景
我们来看看统一的样例dart代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 隐藏状态栏和底部操作栏
SystemChrome.setEnabledSystemUIOverlays([]);
//样例布局代码
return Container(
color: Colors.red,
);
}
}
一下样例都是修改样例布局代码
块来实现效果解释
样例1
Container(color: red);
整个屏幕作为 Container
的父级,并且强制 Container
变成和屏幕一样的大小。所以这个 Container
充满了整个屏幕,并绘制成红色。
样例2
Container(width: 100, height: 100, color: red)
红色的 Container
想要变成 100 x 100 的大小,但是它无法变成,因为屏幕强制它变成和屏幕一样的大小。所以 Container
充满了整个屏幕。
样例3
Center(
child: Container(width: 100, height: 100, color: red),
)
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满了屏幕。
然后 Center
告诉 Container
可以变成任意大小,但是不能超出屏幕。现在,Container
可以真正变成 100 × 100 大小了。
样例4
Align(
alignment: Alignment.bottomRight,
child: Container(width: 100, height: 100, color: red),
)
与上一个样例不同的是,我们使用了 Align
而不是 Center
。
Align
同样也告诉 Container
,你可以变成任意大小。但是,如果还留有空白空间的话,它不会居中 Container
。相反,它将会在允许的空间内,把 Container
放在右下角(bottomRight)。
样例5
Center(
child: Container(
width: double.infinity, height: double.infinity, color: red),
)
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满屏幕。
然后 Center
告诉 Container
可以变成任意大小,但是不能超出屏幕。现在,Container
想要无限的大小,但是由于它不能比屏幕更大,所以就仅充满屏幕。
样例6
Center(
child: Container(color: red),
)
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满屏幕。
然后 Center
告诉 Container
可以变成任意大小,但是不能超出屏幕。由于 Container
没有子级而且没有固定大小,所以它决定能有多大就有多大,所以它充满了整个屏幕。
但是,为什么 Container
做出了这个决定?非常简单,因为这个决定是由 Container
widget 的创建者决定的。可能会因创造者而异,而且你还得阅读 Container
文档 来理解不同场景下它的行为。
样例7
Center(
child: Container(
color: red,
child: Container(color: green, width: 30, height: 30),
),
)
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满屏幕。
然后 Center
告诉红色的 Container
可以变成任意大小,但是不能超出屏幕。由于 Container
没有固定大小但是有子级,所以它决定变成它 child 的大小。
然后红色的 Container
告诉它的 child 可以变成任意大小,但是不能超出屏幕。
而它的 child 是一个想要 30 × 30 大小绿色的 Container
。由于红色的 Container
和其子级一样大,所以也变为 30 × 30。由于绿色的 Container
完全覆盖了红色 Container
,所以你看不见它了。
样例8
Center(
child: Container(
padding: const EdgeInsets.all(20.0),
color: red,
child: Container(color: green, width: 30, height: 30),
),
)
红色 Container
变为其子级的大小,但是它将其 padding 带入了约束的计算中。所以它有一个 30 x 30 的外边距。由于这个外边距,所以现在你能看见红色了。而绿色的 Container
则还是和之前一样。
样例9
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 10, height: 10),
)
你可能会猜想 Container
的尺寸会在 70 到 150 像素之间,但并不是这样。 ConstrainedBox
仅对其从其父级接收到的约束下施加其他约束。
在这里,屏幕迫使 ConstrainedBox
与屏幕大小完全相同,因此它告诉其子 Widget
也以屏幕大小作为约束,从而忽略了其 constraints
参数带来的影响。
样例10
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 10, height: 10),
),
)
现在,Center
允许 ConstrainedBox
达到屏幕可允许的任意大小。 ConstrainedBox
将 constraints
参数带来的约束附加到其子对象上。
Container 必须介于 70 到 150 像素之间。虽然它希望自己有 10 个像素大小,但最终获得了 70 个像素(最小为 70)。
样例11
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 1000, height: 1000),
),
)
现在,Center
允许 ConstrainedBox
达到屏幕可允许的任意大小。 ConstrainedBox
将 constraints
参数带来的约束附加到其子对象上。
Container
必须介于 70 到 150 像素之间。虽然它希望自己有 1000 个像素大小,但最终获得了 150 个像素(最大为 150)。
样例12
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 70,
minHeight: 70,
maxWidth: 150,
maxHeight: 150,
),
child: Container(color: red, width: 100, height: 100),
),
)
现在,Center
允许 ConstrainedBox
达到屏幕可允许的任意大小。 ConstrainedBox
将 constraints
参数带来的约束附加到其子对象上。
Container
必须介于 70 到 150 像素之间。虽然它希望自己有 100 个像素大小,因为 100 介于 70 至 150 的范围内,所以最终获得了 100 个像素。
样例13
UnconstrainedBox(
child: Container(color: red, width: 20, height: 50),
)
屏幕强制 UnconstrainedBox
变得和屏幕一样大,而 UnconstrainedBox
允许其子级的 Container
可以变为任意大小。
样例14
UnconstrainedBox(
child: Container(color: red, width: 4000, height: 50),
)
屏幕强制 UnconstrainedBox
变得和屏幕一样大,而 UnconstrainedBox
允许其子级的 Container
可以变为任意大小。
不幸的是,在这种情况下,容器的宽度为 4000 像素,这实在是太大,以至于无法容纳在 UnconstrainedBox
中,因此 UnconstrainedBox
将显示溢出警告(overflow warning)。
样例15
OverflowBox(
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: Container(color: red, width: 4000, height: 50),
)
屏幕强制 OverflowBox
变得和屏幕一样大,并且 OverflowBox
允许其子容器设置为任意大小。
OverflowBox
与 UnconstrainedBox
类似,但不同的是,如果其子级超出该空间,它将不会显示任何警告。
在这种情况下,容器的宽度为 4000 像素,并且太大而无法容纳在 OverflowBox
中,但是 OverflowBox
会全部显示,而不会发出警告。
样例16
UnconstrainedBox(
child: Container(color: Colors.red, width: double.infinity, height: 100),
)
这将不会渲染任何东西,而且你能在控制台看到错误信息。
UnconstrainedBox
让它的子级决定成为任何大小,但是其子级是一个具有无限大小的 Container
。
Flutter 无法渲染无限大的东西,所以它抛出以下错误: BoxConstraints forces an infinite width.
(盒子约束强制使用了无限的宽度)
样例17
UnconstrainedBox(
child: LimitedBox(
maxWidth: 100,
child: Container(
color: Colors.red,
width: double.infinity,
height: 100,
),
),
)
这次你就不会遇到报错了。 UnconstrainedBox
给 LimitedBox
一个无限的大小;但它向其子级传递了最大为 100 的约束。
如果你将 UnconstrainedBox
替换为 Center
,则LimitedBox
将不再应用其限制(因为其限制仅在获得无限约束时才适用),并且容器的宽度允许超过 100。
上面的样例解释了 LimitedBox
和 ConstrainedBox
之间的区别。
样例18
FittedBox(
child: Text('Some Example Text.',textDirection: TextDirection.ltr,)
);
屏幕强制 FittedBox
变得和屏幕一样大,而 Text
则是有一个自然宽度(也被称作 intrinsic 宽度),它取决于文本数量,字体大小等因素。
FittedBox
让 Text
可以变为任意大小。但是在 Text
告诉 FittedBox
其大小后, FittedBox
缩放文本直到填满所有可用宽度。
样例19
const Center(
child: FittedBox(
child: Text('Some Example Text.', textDirection: TextDirection.ltr),
),
)
但如果你将 FittedBox
放进 Center
widget 中会发生什么? Center
将会让 FittedBox
能够变为任意大小,取决于屏幕大小。
FittedBox
然后会根据 Text
调整自己的大小,然后让 Text
可以变为所需的任意大小,由于二者具有同一大小,因此不会发生缩放。
样例20
const Center(
child: FittedBox(
child: Text(
'This is some very very very large text that is too big to fit a regular screen in a single line.'),
textDirection: TextDirection.ltr,
),
)
然而,如果 FittedBox
位于 Center
中,但 Text
太大而超出屏幕,会发生什么?
FittedBox 会尝试根据 Text
大小调整大小,但不能大于屏幕大小。然后假定屏幕大小,并调整 Text
的大小以使其也适合屏幕。
样例21
const Center(
child: Text(
'This is some very very very large text that is too big to fit a regular screen in a single line.',
textDirection: TextDirection.ltr),
)
然而,如果你删除了 FittedBox
, Text
则会从屏幕上获取其最大宽度,并在合适的地方换行。
样例22
FittedBox(
child: Container(
height: 20.0,
width: double.infinity,
color: Colors.red,
),
)
FittedBox
只能在有限制的宽高中对子 widget 进行缩放(宽度和高度不会变得无限大)。否则,它将无法渲染任何内容,并且你会在控制台中看到错误。
样例23
Row(
textDirection: TextDirection.ltr,
children: [
Container(color: red, child: const Text('Hello!', textDirection: TextDirection.ltr,)),
Container(color: green, child: const Text('Goodbye!', textDirection: TextDirection.ltr,)),
],
)
屏幕强制 Row
变得和屏幕一样大,所以 Row
充满屏幕。
和 UnconstrainedBox
一样, Row
也不会对其子代施加任何约束,而是让它们成为所需的任意大小。 Row
然后将它们并排放置,任何多余的空间都将保持空白。
样例24
Row(
textDirection: TextDirection.ltr,
children: [
Container(
color: red,
child: const Text(
'This is a very long text that '
'won\\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
Container(
color: green,
child: const Text(
'Goodbye flutter ui !!!!!!',
textDirection: TextDirection.ltr,
)),
],
)
由于 Row
不会对其子级施加任何约束,因此它的 children 很有可能太大而超出 Row
的可用宽度。在这种情况下, Row
会和 UnconstrainedBox
一样显示溢出警告。
样例25
Row(
textDirection: TextDirection.ltr,
children: [
Expanded(
child: Center(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\\'t fit the lineThis is a very long text that won\\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
),
),
Container(
color: green,
child: const Text(
'Goodbye!',
textDirection: TextDirection.ltr,
)),
],
)
当 Row
的子级被包裹在了 Expanded
widget 之后, Row
将不会再让其决定自身的宽度了。
取而代之的是,Row
会根据所有 Expanded
的子级来计算其该有的宽度。
换句话说,一旦你使用 Expanded
,子级自身的宽度就变得无关紧要,直接会被忽略掉。
样例26
Row(
textDirection: TextDirection.ltr,
children: [
Expanded(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
),
Expanded(
child: Container(
color: green,
child: const Text(
'Goodbye!',
textDirection: TextDirection.ltr,
),
),
),
],
)
如果所有 Row
的子级都被包裹了 Expanded
widget,每一个 Expanded
大小都会与其 flex 因子成比例,并且 Expanded
widget 将会强制其子级具有与 Expanded
相同的宽度。
换句话说,Expanded
忽略了其子 Widget
想要的宽度。
样例27
Row(
textDirection: TextDirection.ltr,
children: [
Flexible(
child: Container(
color: red,
child: const Text(
'This is a very long text that won\\'t fit the line.',
textDirection: TextDirection.ltr,
),
),
),
Flexible(
child: Container(
color: green,
child: const Text(
'Goodbye!',
textDirection: TextDirection.ltr,
),
),
),
],
)
如果你使用 Flexible
而不是 Expanded
的话,唯一的区别是,Flexible
会让其子级具有与 Flexible
相同或者更小的宽度。而 Expanded
将会强制其子级具有和 Expanded
相同的宽度。但无论是 Expanded
还是 Flexible
在它们决定子级大小时都会忽略其宽度。
这意味着,
Row
要么使用子级的宽度,要么使用Expanded
和Flexible
从而忽略子级的宽度。
样例28
MaterialApp(
home: Scaffold(
body: Container(
color: blue,
child: Column(
children: const [
Text('Hello!'),
Text('Goodbye!'),
],
),
),
))
屏幕强制 Scaffold
变得和屏幕一样大,所以 Scaffold
充满屏幕。然后 Scaffold
告诉 Container
可以变为任意大小,但不能超出屏幕。
当一个 widget 告诉其子级可以比自身更小的话,我们通常称这个 widget 对其子级使用 宽松约束(loose)。
样例29
MaterialApp(
home: Scaffold(
body: SizedBox.expand(
child: Container(
color: blue,
child: Column(
children: const [
Text('Hello!'),
Text('Goodbye!'),
],
),
),
),
)
)
如果你想要 Scaffold
的子级变得和 Scaffold
本身一样大的话,你可以将这个子级外包裹一个 SizedBox.expand
。
当一个 widget 告诉它的子级必须变成某个大小的时候,我们通常称这个 widget 对其子级使用 严格约束(tight)。
严格约束(Tight)VS宽松约束(Loose)
以后你经常会听到一些约束为严格约束或宽松约束,你花点时间来弄明白它们是值得的。
严格约束给你了一种获得确切大小的选择。换句话来说就是,它的最大/最小宽度是一致的,高度也一样。
一个 宽松 约束,换句话来说就是设置了最大宽度/高度,但是让允许其子 widget 获得比它更小的任意大小。换句话来说,宽松约束的最小宽度/高度为 0。
以上是关于一文彻底理解Flutter布局约束的主要内容,如果未能解决你的问题,请参考以下文章