對於Flutter學習者來講,掌握Flutter的佈局行爲,直接決定了開發者在佈局的時候是否能作到高效、快速的開發,可是初學者面對茫茫多的Widget以及各類沒法預料的佈局行爲,老是很難將心中所想,轉化爲Flutter的代碼。git
本文翻譯整理自https://flutter.dev/docs/development/ui/layout/constraints順便插句話,個人開源項目Flutter_dojo,剛發佈了2.0,歡迎你們體驗。 Flutter_dojo github
歡迎你們體驗。當學習Flutter的人問你,爲何寬度爲100的某些小部件在顯示的時候,寬度不爲100像素時,你的默認答案是告訴他們將小部件放在Center內,對嗎?算法
不要這樣作。若是這樣作,他們會一次又一次地回來,詢問爲何某些FittedBox不起做用,爲何Column溢出了,或者IntrinsicWidth應該作什麼。bash
相反,請先告訴他們Flutter佈局與HTML佈局(多是他們很是熟悉的)有很大不一樣,而後讓他們記住如下規則:函數
Constraints go down. Sizes go up. Parent sets position。佈局
若是不瞭解此規則,就沒法真正理解Flutter的佈局,所以Flutter開發人員應儘早學習。學習
更詳細地: Widget從其父級得到本身的約束。約束只是一組4個雙精度數:字體
而後Widget遍歷它的全部子Widget。Widget一個接一個地告訴其孩子約束(每一個孩子可能有所不一樣),而後詢問每一個孩子想要的大小,而後,Widget將其孩子定位(水平地在x軸上佈局,垂直地在y軸上佈局),最後,該小部件將其自身的大小告訴父級(固然,在原始約束內)。flex
例如,若是一個組合Widget包含帶有一些Padding和Column,而且但願如圖所示佈置其兩個Widget:ui
談判是這樣的:
因爲上述佈局規則,Flutter的佈局引擎具備一些重要限制:
因爲父級的大小和位置又取決於其父級,所以在不考慮整個樹的狀況下就沒法精肯定義任何小部件的大小和位置。
每一個widget不必定會獲得它指望的佈局大小,這方面顯著的例子是ConstrainedBox,很容易讓人困惑。 每一個widget不能決定在屏幕中的位置,由父元素決定 由於這種佈局邏輯須要層層考慮上層元素,因此一個元素的最終佈局須要考慮整個UI裏widget樹。 若是爲了精確局部佈局,Container和ConstrainedBox會是一個可行的修飾佈局。
下面的29個示例,將演示Flutter的佈局思想。
Container(color: Colors.red)
複製代碼
屏幕是Container的父級,它強制容器與屏幕的尺寸徹底相同。 所以,容器將屏幕填滿並塗成紅色。
Container(width: 100, height: 100, color: Colors.red)
複製代碼
想要紅色的容器爲100×100,但不是,由於屏幕會強制使其尺寸與屏幕徹底相同。 所以,容器充滿了屏幕。
Center(
child: Container(width: 100, height: 100, color: Colors.red)
)
複製代碼
屏幕會強制Center與屏幕徹底相同,所以Center會填滿整個屏幕。 Center告訴Container它能夠是所需的任何大小,但不能大於屏幕大小。 因此如今容器確實能夠是100×100。
Align(
alignment: Alignment.bottomRight,
child: Container(width: 100, height: 100, color: Colors.red),
)
複製代碼
這與上一個示例不一樣,由於它使用Align而不是Center。 Align一樣告訴Container它能夠是任何所需的大小,同時會在剩餘的可用空間中bottom-right對齊。
Center(
child: Container(
color: Colors.red,
width: double.infinity,
height: double.infinity,
)
)
複製代碼
屏幕會強制Center與屏幕徹底相同,所以Center會填滿整個屏幕。 Center告訴Container它能夠是所需的任何大小,但不能大於屏幕大小。 容器但願具備無限大小,但因爲不能大於屏幕,所以只能填充屏幕。
Center(child: Container(color: Colors.red))
複製代碼
屏幕會強制Center與屏幕徹底相同,所以Center會填滿整個屏幕。 Center告訴Container它能夠是所需的任何大小,但不能大於屏幕大小。 因爲該Container沒有Child且沒有固定的大小,所以它決定要儘量大,所以將其填滿整個屏幕。 可是Container爲何要這樣決定呢?僅僅是由於這是建立Container的人的設計決定。 其它的Widget的建立方式可能有所不一樣,具體取決於狀況。
Center(
child: Container(
color: Colors.red,
child: Container(color: Colors.green, width: 30, height: 30),
)
)
複製代碼
屏幕會強制Center與屏幕徹底相同,所以Center會填滿整個屏幕。 Center告訴紅色Container它能夠是所需的任何大小,但不大於屏幕。 因爲紅色的Container沒有大小,可是有一個Child,所以它決定要與孩子的大小相同。 紅色的Container告訴其子項能夠是它想要的任何大小,但不能大於屏幕大小。 這個Child是一個綠色的Container,它但願是30×30。考慮到紅色Container的大小與其孩子的大小相同,它也是30×30,因此紅色是不可見的,由於綠色的Container會徹底覆蓋紅色Container。
Center(
child: Container(
color: Colors.red,
padding: const EdgeInsets.all(20.0),
child: Container(color: Colors.green, width: 30, height: 30),
)
)
複製代碼
紅色的Container會根據孩子的尺寸自行調整大小,但會考慮本身的padding。 所以它也是30×30加上padding。 因爲有padding,所以能夠看到紅色,綠色Container與上一個示例中的大小相同。
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也假定屏幕大小,從而忽略了其約束參數。
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未來自其約束參數的附加約束施加到其子對象上。 Container必須介於70到150像素之間。 它但願有10個像素,因此最終有70個像素(最小)。
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未來自其約束參數的附加約束施加到其子對象上。 Container必須介於70到150像素之間。 它但願有1000個像素,因此最終有150個(最大)。
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未來自其約束參數的附加約束施加到其子對象上。 Container必須介於70到150像素之間。它但願有100像素,這就是它的大小,由於它介於70到150之間。
UnconstrainedBox(
child: Container(color: Colors.red, width: 20, height: 50),
)
複製代碼
屏幕強制UnconstrainedBox與屏幕大小徹底相同。 可是,UnconstrainedBox容許其子Container設置任意大小。
UnconstrainedBox(
child: Container(color: Colors.red, width: 4000, height: 50),
)
複製代碼
屏幕強制UnconstrainedBox與屏幕大小徹底相同,UnconstrainedBox將其子Container設爲任意大小。 不幸的是,在這種狀況下,容器的寬度爲4000像素,太大而沒法容納在UnconstrainedBox中,所以UnconstrainedBox顯示溢出警告。
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相似,但不一樣的是,若是Child不適合該空間,它將不會顯示任何警告。 在這種狀況下,容器的寬度爲4000像素,而且太大而沒法容納在OverflowBox中,可是OverflowBox會盡量地顯示儘量多的內容,而不會發出警告。
UnconstrainedBox(
child: Container(
color: Colors.red,
width: double.infinity,
height: 100,
)
)
複製代碼
你會在控制檯中看到錯誤。 UnconstrainedBox可讓它的子Widget具備所需的任何大小,可是其子Widget是一個具備無限大小的Container。 Flutter沒法呈現無限大小,所以會出現如下錯誤消息:BoxConstraints forces an infinite width.
UnconstrainedBox(
child: LimitedBox(
maxWidth: 100,
child: Container(
color: Colors.red,
width: double.infinity,
height: 100,
)
)
)
複製代碼
這樣就不會再出現錯誤,由於當UnconstrainedBox爲LimitedBox賦予無限大小時,它向下傳遞的約束爲最大寬度是100像素。 若是你將UnconstrainedBox替換爲Center,則LimitedBox將再也不應用其限制(由於其限制僅在得到無限約束時才適用),而且容器的寬度容許超過100。 這解釋了LimitedBox和ConstrainedBox之間的區別。
FittedBox(
child: Text('Some Example Text.'),
)
複製代碼
屏幕將強制FittedBox與屏幕徹底相同。 文本將根據寬度調整自有的寬度屬性,字體屬性等。 FittedBox容許文本的尺寸爲任意大小,但在將文本告知FittedBox大小後,FittedBox縮放文本直到填滿全部可用寬度。
Center(
child: FittedBox(
child: Text('Some Example Text.'),
)
)
複製代碼
可是,若是將FittedBox放在Center內會怎樣?Center會將FittedBox設置爲所需的任何大小,直至屏幕大小。 而後,將FittedBox調整爲Text大小,並讓Text爲所需的任何大小。 因爲FittedBox和Text具備相同的大小,所以不會發生縮放。
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中,但文本太大而沒法容納屏幕,會發生什麼? FittedBox會嘗試根據文本大小調整大小,但不能大於屏幕大小。而後假定屏幕大小,並調整文本的大小以使其也適合屏幕。
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從屏幕上獲取其最大寬度,並在合適 的地方換行。
FittedBox(
child: Container(
height: 20.0,
width: double.infinity,
)
)
複製代碼
FittedBox只能在有限制的寬高中進行Child的縮放(寬度和高度非無限大)。 不然,它將沒法呈現任何內容,而且你會在控制檯中看到錯誤。
Row(
children:[
Container(color: Colors.red, child: Text('Hello!')),
Container(color: Colors.green, child: Text('Goodbye!')),
]
)
複製代碼
屏幕強制行與屏幕大小徹底相同。 就像UnconstrainedBox同樣,Row不會對其子代施加任何約束,而是讓它們成爲所需的任意大小。Row而後將它們並排放置,任何多餘的空間都將保持空白。
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不會對其子級施加任何約束,所以子Widget頗有可能太大而沒法容納Row的可用寬度。 在這種狀況下,就像UnconstrainedBox同樣,Row會顯示溢出警告。
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的子Child被包裹在Expanded中時,Row將再也不讓該Child定義本身的寬度。 取而代之的是,Row會根據全部Expanded的Child來計算其該有的寬度。 換句話說,一旦您使用Expanded,原始Widget的寬度就變得可有可無,而且會被忽略。
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的子Widget都包裝在Expeded中,則每一個Expeded的大小均與其flex參數成比例,子Child會設置爲計算的Expanded寬度。 換句話說,Expanded忽略了其子Widget寬度。
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使其子元素的寬度等於或小於其自身的寬度,而Expanded強制其子元素具備與Expeded徹底相同的寬度。 可是,在調整尺寸時,Expanded和Flexible的都忽略了孩子的寬度。
注意:這意味着,Row要麼使用子Child的寬度,要麼使用Expanded和Flexible從而忽略Child的寬度。
Scaffold(
body: Container(
color: blue,
child: Column(
children: [
Text('Hello!'),
Text('Goodbye!'),
]
)))
複製代碼
屏幕會強制設置Scaffold與屏幕大小徹底相同,所以Scaffold會填滿屏幕。 Scaffold告訴容器它能夠是所需的任何大小,但不能大於屏幕大小。
注意:當Widget告訴其子Widget它能夠小於特定大小時,咱們說該Widget爲其Child提供了loose約束。
Scaffold(
body: SizedBox.expand(
child: Container(
color: blue,
child: Column(
children: [
Text('Hello!'),
Text('Goodbye!'),
],
))))
複製代碼
若是你但願Scaffold的子Widget與本身的Scaffold大小徹底相同,則可使用SizedBox.expand包裝其Child。
注意:當小部件告訴其子級必須具備必定大小時,咱們說該小部件爲其子級提供了tight約束。
前面常常提到一些約束是tight或loose,因此你值得知道這是什麼意思。
tight constraint提供了一種可能性,即確切的大小。換句話說,tight constraint的最大寬度等於其最小寬度。 而且其最大高度等於其最小高度。
若是轉到Flutter的box.dart文件並搜索BoxConstraints構造函數,則會發現如下內容:
BoxConstraints.tight(Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;
複製代碼
若是你從新查看上面的示例2,它將告訴咱們屏幕強制紅色Container與屏幕徹底相同。 固然,屏幕是經過將tight constraint傳遞給Container來實現的。
另外一方面,寬鬆的約束設置了最大寬度和高度,但使小部件儘量小。 換句話說,寬鬆約束的最小寬度和高度都等於零:
BoxConstraints.loose(Size size)
: minWidth = 0.0,
maxWidth = size.width,
minHeight = 0.0,
maxHeight = size.height;
複製代碼
若是您從新查看示例3,它將告訴咱們Center使紅色Container變得更小,但不大於屏幕。 Center經過向Container傳遞loose constraint來作到這一點。 最終,Center的主要目的是將其從父級(屏幕)得到的tight constraint轉換爲對其子級(容器)的loose constraint。
知道通常的佈局規則是必要的,但這還不夠。 每一個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的渲染對象。如今導航到RenderFlex的源代碼,將您帶到flex.dart文件。 向下滾動,直到找到一個名爲performLayout()的方法。 這是執行列布局的方法。
對Flutter感興趣的朋友能夠加入個人Flutter修仙羣。