必读 | 深入理解 Flutter 的布局约束
前言
本文最初来自于社区成员 Marcelo Glasberg 在 Medium 发表的文章——"初学者应知应会的 Flutter 高级布局规则 (Flutter: The Advanced Layout Rule Even Beginners Must Know)",然后被 Flutter Team 发现并收录到 Flutter 官方文档,后经中文社区成员翻译发布至 Flutter 中文文档网站,如果您对本文有任何建议和讨论,请点击阅读原文到 GitHub Issue 中反馈。
在认真阅读完这篇文章后,我认为它对 Flutter 开发者来说具有相当的 指导意义,每一位 Flutter 开发都应该认真理解其中的布局约束过程,是非常必要的。因此,在翻译本文的过程中,我们对译文反复打磨,尽可能保留原文想传递给读者的内容,希望让每一位看到此文的开发者都能够有所收获。
深入理解布局约束
我们会经常听到一些开发者在学习 Flutter 时的疑惑:为什么我设置了 width:100
, 但是看上去却不是 100 像素宽呢。(注意,本文中的“像素”均指的是逻辑像素) 通常你会回答,将这个 widget 放进 Center
中,对吧?
别这么干。
如果你这样做了,他们会不断找你询问这样的问题:为什么 FittedBox
又不起作用了? 为什么 Column
又溢出边界,亦或是 IntrinsicWidth
应该做什么。
其实我们首先应该做的,是告诉他们 Flutter 的布局方式与 HTML 的布局差异相当大 (这些开发者很可能是 Web 开发者),然后要建议他们熟记这条规则:
- 首先,上层 widget 向下层 widget 传递约束条件。
- 然后,下层 widget 向上层 widget 传递大小信息。
- 最后,上层 widget 决定下层 widget 的位置。
如果我们在开发时无法熟练运用这条规则,在布局时就不能完全理解其原理,所以越早掌握这条规则越好!
更多细节:
Widget 会通过它的 父级 获得自身的约束。 约束实际上就是 4 个浮点类型的集合: 最大/最小宽度,以及最大/最小高度。
然后,这个 widget 将会逐个遍历它的 children 列表。向子级传递 约束(子级之间的约束可能会有所不同),然后询问它的每一个子级需要用于布局的大小。
然后,这个 widget 就会对它子级的 children 逐个进行布局。 (水平方向是
x
轴,竖直是y
轴)最后,widget 将会把它的大小信息向上传递至父 widget(包括其原始约束条件)。
例如,如果一个 widget 中包含了一个具有 padding 的 Column, 并且要对 Column 的子 widget 进行如下的布局:
那么谈判将会像这样:
Widget: “嘿!我的父级。我的约束是多少?”
Parent: “你的宽度必须在 80 到 300 像素之间,高度必须在 30 到 85 之间。”
Widget: “嗯…我想要 5 个像素的内边距,这样我的子级能最多拥有 290 个像素宽度和 75 个像素高度。”
Widget: “嘿,我的第一个子级,你的宽度必须要在 0 到 290,长度在 0 到 75 之间。”
First child: “OK,那我想要 290 像素的宽度,20 个像素的长度。”
Widget: “嗯…由于我想要将我的第二个子级放在第一个子级下面,所以我们仅剩 55 个像素的高度给第二个子级了。”
Widget: “嘿,我的第二个子级,你的宽度必须要在 0 到 290,长度在 0 到 55 之间。”
Second child: “OK,那我想要 140 像素的宽度,30 个像素的长度。”
Widget: “很好。我的第一个子级将被放在 x: 5 & y: 5 的位置,而我的第二个子级将在 x: 80 & y: 25 的位置。”
Widget: “嘿,我的父级,我决定我的大小为 300 像素宽度,60 像素高度。”
限制
正如上述所介绍的布局规则中所说的那样, Flutter 的布局引擎有一些重要限制:
一个 widget 仅在其父级给其约束的情况下才能决定自身的大小。 这意味着 widget 通常情况下 不能任意获得其想要的大小。
一个 widget 无法知道,也不需要决定其在屏幕中的位置。 因为它的位置是由其父级决定的。
当轮到父级决定其大小和位置的时候,同样的也取决于它自身的父级。 所以,在不考虑整棵树的情况下,几乎不可能精确定义任何 widget 的大小和位置。
样例
以下的效果图片和样例代码均可通过下面这个 DartPad 链接查看: dartpad.cn/759e9e061a6d4c247700429ddda09b8b
也可以在这个 GitHub 仓库中获得: github.com/marcglasberg/flutter_layout_article
以下将依次介绍这些示例:
样例 1
Container(color: Colors.red)
整个屏幕作为 Container
的父级,并且强制 Container
变成和屏幕一样的大小。
所以这个 Container
充满了整个屏幕,并绘制成红色。
样例 2
Container(width: 100, height: 100, color: Colors.red)
红色的 Container
想要变成 100 x 100 的大小, 但是它无法变成,因为屏幕强制它变成和屏幕一样的大小。
所以 Container
充满了整个屏幕。
样例 3
Center(child: Container(width: 100, height: 100, color: Colors.red)
)
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满了屏幕。
然后 Center
告诉 Container
可以变成任意大小,但是不能超出屏幕。 现在,Container
可以真正变成 100 × 100 大小了。
样例 4
Align(alignment: Alignment.bottomRight,child: Container(width: 100, height: 100, color: Colors.red),
)
与上一个样例不同的是,我们使用了 Align
而不是 Center
。
Align
同样也告诉 Container
,你可以变成任意大小。 但是,如果还留有空白空间的话,它不会居中 Container
。 相反,它将会在允许的空间内,把 Container
放在右下角(bottomRight)。
样例 5
Center(child: Container(color: Colors.red,width: double.infinity,height: double.infinity,)
)
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满屏幕。
然后 Center
告诉 Container
可以变成任意大小,但是不能超出屏幕。 现在,Container
想要无限的大小,但是由于它不能比屏幕更大, 所以就仅充满屏幕。
样例 6
Center(child: Container(color: Colors.red))
屏幕强制 Center
变得和屏幕一样大,所以 Center
充满屏幕。
然后 Center
告诉 Container
可以变成任意大小,但是不能超出屏幕。 由于 Container
没有子级而且没有固定大小,所以它决定能有多大就有多大, 所以它充满了整个屏幕。
但是,为什么 Container
做出了这个决定? 非常简单,因为这个决定是由 Container
widget 的创建者决定的。 可能会因创造者而异,而且你还得阅读 Container
文档 来理解不同场景下它的行为。
样例 7
Center(child: Container(color: Colors.red,child: Container(color: Colors.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(color: Colors.red,padding: const EdgeInsets.all(20.0),child: Container(color: Colors.green, width: 30, height: 30),)
)
红色 Container
变为其子级的大小,但是它将其 padding 带入了约束的计算中。 所以它有一个 30 x 30 的外边距。由于这个外边距,所以现在你能看见红色了。 而绿色的 Container
则还是和之前一样。
样例 9
ConstrainedBox(constraints: BoxConstraints(minWidth: 70, minHeight: 70,maxWidth: 150, maxHeight: 150,),child: Container(color: Colors.red, width: 10, height: 10),
)
你可能会猜想 Container
的尺寸会在 70 到 150 像素之间,但并不是这样。 ConstrainedBox
仅对其从其父级接收到的约束下施加其他约束。
在这里,屏幕迫使 ConstrainedBox
与屏幕大小完全相同, 因此它告诉其子 Widget
也以屏幕大小作为约束, 从而忽略了其 constraints
参数带来的影响。
样例 10
Center(child: ConstrainedBox(constraints: BoxConstraints(minWidth: 70, minHeight: 70,maxWidth: 150, maxHeight: 150,),child: Container(color: Colors.red, width: 10, height: 10),)
)
现在,Center
允许 ConstrainedBox
达到屏幕可允许的任意大小。 ConstrainedBox
将 constraints
参数带来的约束附加到其子对象上。
Container 必须介于 70 到 150 像素之间。虽然它希望自己有 10 个像素大小, 但最终获得了 70 个像素(最小为 70)。
样例 11
Center(child: ConstrainedBox(constraints: BoxConstraints(minWidth: 70, minHeight: 70,maxWidth: 150, maxHeight: 150,),child: Container(color: Colors.red, width: 1000, height: 1000),)
)
现在,Center
允许 ConstrainedBox
达到屏幕可允许的任意大小。 ConstrainedBox
将 constraints
参数带来的约束附加到其子对象上。
Container
必须介于 70 到 150 像素之间。 虽然它希望自己有 1000 个像素大小, 但最终获得了 150 个像素(最大为 150)。
样例 12
Center(child: ConstrainedBox(constraints: BoxConstraints(minWidth: 70, minHeight: 70,maxWidth: 150, maxHeight: 150,),child: Container(color: Colors.red, width: 100, height: 100),)
)
现在,Center
允许 ConstrainedBox
达到屏幕可允许的任意大小。 ConstrainedBox
将 constraints
参数带来的约束附加到其子对象上。
Container
必须介于 70 到 150 像素之间。 虽然它希望自己有 100 个像素大小, 因为 100 介于 70 至 150 的范围内,所以最终获得了 100 个像素。
样例 13
UnconstrainedBox(child: Container(color: Colors.red, width: 20, height: 50),
)
屏幕强制 UnconstrainedBox
变得和屏幕一样大,而 UnconstrainedBox
允许其子级的 Container
可以变为任意大小。
样例 14
UnconstrainedBox(child: Container(color: Colors.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: Colors.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.'),
)
屏幕强制 FittedBox
变得和屏幕一样大, 而 Text
则是有一个自然宽度(也被称作 intrinsic 宽度), 它取决于文本数量,字体大小等因素。
FittedBox
让 Text
可以变为任意大小。 但是在 Text
告诉 FittedBox
其大小后, FittedBox
缩放文本直到填满所有可用宽度。
样例 19
Center(child: FittedBox(child: Text('Some Example Text.'),)
)
但如果你将 FittedBox
放进 Center
widget 中会发生什么? Center
将会让 FittedBox
能够变为任意大小, 取决于屏幕大小。
FittedBox
然后会根据 Text
调整自己的大小, 然后让 Text
可以变为所需的任意大小, 由于二者具有同一大小,因此不会发生缩放。
样例 20
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.'),)
)
然而,如果 FittedBox
位于 Center
中, 但 Text
太大而超出屏幕,会发生什么?
FittedBox 会尝试根据 Text
大小调整大小, 但不能大于屏幕大小。然后假定屏幕大小, 并调整 Text
的大小以使其也适合屏幕。
样例 21
Center(child: Text('This is some very very very large text that is too big to fit a regular screen in a single line.'),
)
然而,如果你删除了 FittedBox
, Text
则会从屏幕上获取其最大宽度, 并在合适的地方换行。
样例 22
FittedBox(child: Container(height: 20.0, width: double.infinity,)
)
FittedBox
只能在有限制的宽高中 对子 widget 进行缩放(宽度和高度不会变得无限大)。 否则,它将无法渲染任何内容,并且你会在控制台中看到错误。
样例 23
Row(children:[Container(color: Colors.red, child: Text('Hello!')),Container(color: Colors.green, child: Text('Goodbye!')),]
)
屏幕强制 Row
变得和屏幕一样大,所以 Row
充满屏幕。
和 UnconstrainedBox
一样, Row
也不会对其子代施加任何约束, 而是让它们成为所需的任意大小。 Row
然后将它们并排放置, 任何多余的空间都将保持空白。
样例 24
Row(children:[Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.')),Container(color: Colors.green, child: Text('Goodbye!')),]
)
由于 Row
不会对其子级施加任何约束, 因此它的 children 很有可能太大 而超出 Row
的可用宽度。在这种情况下, Row
会和 UnconstrainedBox
一样显示溢出警告。
样例 25
Row(children:[Expanded(child: Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.'))),Container(color: Colors.green, child: Text('Goodbye!')),]
)
当 Row
的子级被包裹在了 Expanded
widget 之后, Row
将不会再让其决定自身的宽度了。
取而代之的是,Row
会根据所有 Expanded
的子级 来计算其该有的宽度。
换句话说,一旦你使用 Expanded
, 子级自身的宽度就变得无关紧要,直接会被忽略掉。
样例 26
Row(children:[Expanded(child: Container(color: Colors.red, child: Text(‘This is a very long text that won’t fit the line.’)),),Expanded(child: Container(color: Colors.green, child: Text(‘Goodbye!’),),]
)
如果所有 Row
的子级都被包裹了 Expanded
widget, 每一个 Expanded
大小都会与其 flex 因子成比例, 并且 Expanded
widget 将会强制其子级具有与 Expanded
相同的宽度。
换句话说,Expanded
忽略了其子 Widget
想要的宽度。
样例 27
Row(children:[Flexible(child: Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.'))),Flexible(child: Container(color: Colors.green, child: Text(‘Goodbye!’))),]
)
如果你使用 Flexible
而不是 Expanded
的话, 唯一的区别是,Flexible
会让其子级具有与 Flexible
相同或者更小的宽度。 而 Expanded
将会强制其子级具有和 Expanded
相同的宽度。 但无论是 Expanded
还是 Flexible
在它们决定子级大小时都会忽略其宽度。
这意味着,
Row
要么使用子级的宽度, 要么使用Expanded
和Flexible
从而忽略子级的宽度。
样例 28
Scaffold(body: Container(color: blue,child: Column(children: [Text('Hello!'),Text('Goodbye!'),])))
屏幕强制 Scaffold
变得和屏幕一样大, 所以 Scaffold
充满屏幕。 然后 Scaffold
告诉 Container
可以变为任意大小, 但不能超出屏幕。
当一个 widget 告诉其子级可以比自身更小的话, 我们通常称这个 widget 对其子级使用 宽松约束(loose)。
样例 29
Scaffold(
body: SizedBox.expand(child: Container(color: blue,child: Column(children: [Text('Hello!'),Text('Goodbye!'),],))))
如果你想要 Scaffold
的子级变得和 Scaffold
本身一样大的话, 你可以将这个子级外包裹一个 SizedBox.expand
。
当一个 widget 告诉它的子级必须变成某个大小的时候, 我们通常称这个 widget 对其子级使用 严格约束(tight)。
严格约束(Tight) vs 宽松约束(loose)
以后你经常会听到一些约束为严格约束或宽松约束, 你花点时间来弄明白它们是值得的。
严格约束给你了一种获得确切大小的选择。 换句话来说就是,它的最大/最小宽度是一致的,高度也一样。
如果你到 Flutter 的 box.dart
文件中搜索 BoxConstraints
构造器,你会发现以下内容:
BoxConstraints.tight(Size size): minWidth = size.width,maxWidth = size.width,minHeight = size.height,maxHeight = size.height;
如果你重新阅读 样例 2, 它告诉我们屏幕强制 Container
变得和屏幕一样大。 为何屏幕能够做到这一点, 原因就是给 Container
传递了严格约束。
一个宽松约束换句话来说就是设置了最大宽度/高度, 但是让允许其子 widget 获得比它更小的任意大小。 换句话来说,宽松约束的最小宽度/高度为 0。
BoxConstraints.loose(Size size): minWidth = 0.0,maxWidth = size.width,minHeight = 0.0,maxHeight = size.height;
如果你访问 样例 3, 它将会告诉我们 Center
让红色的 Container
变得更小, 但是不能超出屏幕。Center
能够做到这一点的原因就在于 给 Container
的是一个宽松约束。 总的来说,Center
起的作用就是从其父级(屏幕)那里获得的严格约束, 为其子级(Container
)转换为宽松约束。
了解如何为特定 widget 制定布局规则
掌握通用布局是非常重要的,但这还不够。
应用一般规则时,每个 widget 都具有很大的自由度, 所以没有办法只看 widget 的名称就知道可能它长什么样。
如果你尝试推测,可能就会猜错。 除非你已阅读 widget 的文档或研究了其源代码, 否则你无法确切知道 widget 的行为。
布局源代码通常很复杂,因此阅读文档是更好的选择。 但是当你在研究布局源代码时,可以使用 IDE 的导航功能轻松找到它。
下面是一个例子:
在你的代码中找到一个
Column
并跟进到它的源代码。 为此,请在 (Android Studio/IntelliJ) 中使用command+B
(macOS)或control+B
(Windows/Linux)。 你将跳到basic.dart
文件中。由于Column
扩展了Flex
, 请导航至Flex
源代码(也位于basic.dart
中)。向下滚动直到找到一个名为
createRenderObject()
的方法。 如你所见,此方法返回一个RenderFlex
。 它是Column
的渲染对象, 现在导航到flex.dart
文件中的RenderFlex
的源代码。向下滚动,直到找到
performLayout()
方法, 由该方法执行列布局。
致谢
本文的顺利发布需感谢以下社区成员的支持:Xinlei Wang, Marcelo Glasberg, Simon Lightfoot, Luke, Alex, CaiJingLong, Yujie Ren, Kai Sun, Lynn Wang,谢谢!
如果您对本文有任何建议和讨论,请在 GitHub Issue 中 反馈,希望查看最新本文,请到 flutter.cn 查看: flutter.cn/docs/development/ui/layout/constraints
必读 | 深入理解 Flutter 的布局约束相关推荐
- 深入理解布局约束 | 开发者说·DTalk
本文原作者: Xinlei (译),原文发布于微信公众号: Flutter社区 https://mp.weixin.qq.com/s/2GFKxfAtnOozLsUiRUQPHg 前言 本文最初来自 ...
- 初识Flutter之搞定布局约束
[版权申明]非商业目的注明出处可自由转载 博文地址:https://blog.csdn.net/ShuSheng0007/article/details/110292757 出自:shusheng00 ...
- Flutter基础布局组件及实现
https://www.cnblogs.com/lxlx1798/p/11084904.html 一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Paddi ...
- Flutter 弹性布局的基石: flex 和 flexible
Flutter 弹性布局的基石 是 flex 和 flexible.理解了这两个 widget,后面的row,column 就都轻而易举了.本文用示例的方式详细介绍 flex 的布局算法. flex ...
- 深入理解 Flutter 框架层次结构
作者: Frederik Schweiger 链接 : The Layer Cake Flutter 是一个非常优秀的跨平台开发框架,基于 Flutter 我们可以用很少的代码快速的开发出界面精美的 ...
- Flutter进阶—布局一个控件
要在Flutter中布局单个控件,创建一个简单的控件并将其显示在屏幕上.在Flutter中,将文本.图标或图像放在屏幕上只需几步. 1.选择一个布局控件来保存对象 根据您希望对齐或约束可见控件的方式, ...
- Flutter进阶—布局方法演示
Flutter的布局机制的核心是控件.在Flutter中,几乎所有的东西都是一个控件,甚至布局模型都是控件.您在Flutter应用程序中看到的图像.图标和文本都是控件.但是您看不到的东西也是控件,例如 ...
- 开启Fluter基础之旅三-------Material Design风格组件、Cupertino风格组件、Flutter页面布局篇...
Material Design风格组件: 继续接着上一次https://www.cnblogs.com/webor2006/p/12545701.html的Material Design进行学习. A ...
- 第一个Xcode项目 - 代码修改布局约束
第一行的选中效果已经有了,那第二行的选中效果怎么做呢? 我这里选择改变布局约束来实现选中效果 [我有个用object-c做APP的同事他说,我觉得这个应该去获取色块的位置,然后赋给选中用的View,然 ...
最新文章
- 安装envi出现cannot find lincese_Ubuntu 16.04 安装 CUDA10.1 (解决循环登陆的问题)
- toch_geometric 笔记:message passing GCNConv
- Hadoop版本选择探讨
- Oracle提高SQL查询效率where语句条件的先后次序
- 运行报错error: (-215:Assertion failed) !ssize.empty() in function 'cv::resize'
- rabbitmq-通配符模式
- Codeforces Round #205 (Div. 2) : D
- Hyper-V 2016 系列教程34 在局域网内架设Windows时间服务器
- python fortran混合编程_python fortran c 混合编程
- bz格式linux解压,Linux下tar bz gz等压缩包的压缩和解压
- 地震勘探算法matlab,SeismicLab 地震勘探,matlab程序包, 地球物理, 学。作图工具等。 249万源代码下载- www.pudn.com...
- 主成分与因子分析异同_主成分和因子分析原理及比较
- 李嘉诚的经典名言,年轻人如何理财
- OpenJ_Bailian - 3164 奇偶排序
- 【CRC笔记】CRC-16 IBM-SDLC C语言实现
- 人声和乐器的频谱范围
- 操作系统笔记——Linux系统实例分析、Windows系统实例分析
- Python爬虫:scrapy爬取斗鱼直播图片
- 万年历农历程序(抄表法)
- ASP.NET(C#)常用数据加密和解密方法
热门文章
- Unfiltered Audio Plugins Bundle for Mac(音频插件包)附破解教程 v2.0.0激活版
- java 防XSS过滤处理过滤器
- 2023计算机毕业设计SSM最新选题之java企业部门报销管理g9d62
- 杭州市全日制历届普通高校专科毕业生紧缺专业目录
- 低价位高性价比keychron机器键盘推荐
- yolov3_Assertion `l.outputs == params.inputs‘ failed
- 1826: [JSOI2010]缓存交换
- php网页制作添加线条,html5Canvas实现画直线与设置线条的样式-
- C#编写OPC客户端读取OPC服务器的数据(最高效简洁版)
- python注释多行代码快捷键_python学习笔记(五)---sublime text 多行代码注释快捷键...