Flutter - 深刻佈局規則

本問如今是官方文檔的一部分了

當Fluter初學者問你爲何組件裏的width:100不是100像素的時候,默認的答案就是告訴他們把組件放進一個Center裏,對吧?html

不要這麼幹git

若是你這麼幹了,他們會一次一次的問你爲何FittedBox有問題,爲何Column會overflow,又或者IntrinsicWidth是作什麼的。github

因此,一開始就告訴他們Flutter的佈局和html有很大的不一樣,他們極可能就是html的高手,而後讓他們記住如下的規則:函數

👉 約束(Constraint)向下,大小(Size)向上,位置父決定

不理解這個規則,Flutter的佈局是無法弄清楚的。因此,我(做者)以爲最好今早的學會它。佈局

細節:學習

  • 一個組件都是從它的父組件得到約束(constraint)。一個約束就是四個double值:一個最小、最大寬度和一個最小、最大高度。
  • 而後,這個組件遍歷它的子組件。一個個的通知它的子組件他們的約束(每一個子組件均可能不同),而後詢問他們想要的size。
  • 而後,這個組件沿着橫向的x軸和縱向的y軸排列它的子組件的位置
  • 最後,每一個組件告訴它的父組件它本身在約束下的size

好比一個Column組件,已經設定了padding值,如今要給它的兩個子組件設定佈局:
1_757yPUwLSMwxOSMPUJd7dw.png字體

組件 -- 詢問父組件約束是啥。
父組件 -- 你只能是 90300寬, 3085高。
組件 -- 嗯~~ 我還要5個單位的padding,那麼個人子組件只能有最大 290的寬和 75的高。
組件 -- 嗨,第一個子組件你必須是 0 ~ 290寬, 0 ~ 75高。
第一個子組件 -- 我要 290寬, 20高。
組件 -- 嗯~~ , 既然我要把第二個子組件放在第一個的下面,這樣就剩下 55的高度給第二個子組件了。
組件 -- 嗨,第二個子組件你必須是 0 ~ 290寬, 0 ~ 55高。
第二個子組件 -- 好的,我要 140寬和 30高。
組件 -- 很好,我會把第一個子組件放在 x軸:5y軸:5,第二個子組件 x軸:80y軸:25的位置。
組件 -- 嗨,父組件。個人size是 300寬, 60高。

限制(Limitation)

Flutter佈局引擎在上面規則的基礎上還有一些其餘的限制:flex

  • 一個組件只能夠在父組件傳過來的約束的範圍內肯定它的大小(size)。也就是說,通常一個組件不能想多大就多大
  • 一個組件不知道,也不能決定它在屏幕上的位置。組件的位置是由它的父組件決定的。
  • 父組件的大小和位置也是由它的父組件決定的,只有在的概念下才能決定一個組件的大小和位置。

示例

下面是一個互動示例。spa

原文提到了CodePen,也能夠在如下兩種方法裏選一種。
示例 1

1_6yLUHp92rQtZDSEv9aBQcA.png

Container(color: Colors.red);

屏幕是Container的父組件,它會把紅色的Container嚴絲合縫的約束在整個的屏幕內部。設計

因此,Container填滿了整個屏幕,處處都是紅色。

示例 2

1_6yLUHp92rQtZDSEv9aBQcA.png

Container(color: Colors.red, width: 100, height: 100)

Container想要寬100,高100,可是不行。屏幕會強制它填滿屏幕。

因此Container填滿了屏幕。

示例 3

1_Mwp8fmF4Uce1G6pxuuJNBw.png

屏幕強制Center填滿整個屏幕,因此Center顯示在全屏。

Center告訴Container能夠擁有想要的大小,可是不能比屏幕還大。因此,Container的大小就是100x100。

示例 4

1_GuTTQKTH8LCB1Ha343NbWQ.png

Align(
    alignment: Alignment.bottomRight,
    child: Container(width: 100, height: 100, color: Colors.red)
)

這和前一個例子並不同,這裏用的是Align而不是Center

Align也會告訴Container能夠任意大小,可是若是有任意的可用空間,是不讓Container居中的,它會把Contaienr放在右下角。

示例 5

1_zt-a_hMkHJpLvYGmFdcFsg.png

Center(
    child: Container(
        color: Colors.red,
        width: double.infinity,
        height: double.infinity,
    )
)

屏幕強制Center填充整個屏幕,因此Center填滿了屏幕。

Center告訴Container能夠是任意大小,Container要的是無限大,可是它又不能比屏幕還大,因此也填滿了屏幕。

示例 6

1_FPIri__CN8Gq1DvEobZopg.png

Center(child: Container(color: Colors.red))

屏幕仍是會強制Center填充屏幕。

Center會告訴Container能夠爲任意大小,可是不能比屏幕大。由於Container沒有子組件,也沒有固定的大小。它會決定顯示爲儘量的大,因此填充了屏幕。

可是,爲何Container要這麼決定呢?這是設計決定的。因此,Container在這樣的狀況下會如何顯示,你要查看文檔。

示例 7

1_AYOkoZFkYhmmmmQMo3hrRw.png

Center(  
  child: Container(  
    color: Colors.red,  
    child: Container(color: Colors.green, width: 30, height: 30),  
  )  
)

Center會填充屏幕。

Center會告訴Container能夠任意大小。Container沒有大小,可是有一個子組件,因此它決定和它的子組件同樣大小。

紅色的Container告訴它的子組件能夠爲任意大小,可是不能比屏幕還大。

綠色的Container想要30 x 30。就像上面說的,紅色的Container就會顯示爲綠色的Container的大小,也是30x30。沒有紅色能夠顯示出來,由於綠色的把紅色所有覆蓋住了。

示例 8

1_c3fwXjxtfHl34QyG1MU2ng.png

Center(  
  child: Container(  
    color: Colors.red,  
    padding: const EdgeInsets.all(20.0),  
    child: Container(color: Colors.green, width: 30, height: 30),  
  )  
)

紅色的Container會顯示爲其子組件的大小,可是它本身還有padding因此它自己的大小是70x70(=30x30 + 20的padding值)。最後紅色由於有padding值是可見的,綠色Container和上例同樣有30x30的大小。

示例 9

1_XFr3RxUmfBx0uzP62D5fxA.png

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爲屏幕大小。因此它會告訴它的子組件Container顯示到屏幕的大小,因此constaints參數的值都被忽略了。

示例 10

1_VjsbcsI4VM8uU-H-5_UVhw.png

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之間,Container雖然設定爲10的大小,可是最後仍是顯示爲70(最小值)。

示例 11

1_aZuAYE68PZuUeUBmtI2dNw.png

Center(  
  child: ConstrainedBox(  
    constraints: BoxConstraints(  
      minWidth: 70,  
      minHeight: 70,  
      maxWidth: 150,  
      maxHeight: 150,  
    ),  
    child: Container(color: Colors.red, width: 1000, height: 1000),  
  )  
)

Center容許ConstraintedBox是屏幕內的任意大小。ConstrainedBox會把它的額外約束經過constraints參數傳入給它的子組件。

因此,Container必須是在70到150之間。它想要設定爲1000,因此最後的值爲150(最大值)。

示例 12

1_CCHJKL7Q9J_bGtX7CU5CLA.png

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之間的。

示例 13

1_brAZzN2-S_fDGXiMDUZheQ.png

UnconstrainedBox(  
  child: Container(color: Colors.red, width: 20, height: 50),  
)

屏幕強制UnconstrainedBox擁有和屏幕同樣的大小。而UnconstrainedBox容許它的子組件有任意大小。

示例 14

1_OWv20n8bQInTHZAP1CxMHA.png

UnconstrainedBox(  
child: Container(color: Colors.red, width: 4000, height: 50),  
);

屏幕強制UnconstrainedBox和屏幕同樣大小,而UnconstrainedBox讓它的Container子組件擁有任意大小。

可是,本例中Container設定的是4000的寬,這樣太大了無法放進UnconstrainedBox,因此UnconstrainedBox會顯示出「overflow warning」。

示例 15

1_9JfJofqdzHvOov1vl4NA4A.png

OverflowBox(  
  minWidth: 0.0,  
  minHeight: 0.0,  
  maxWidth: double.infinity,  
  maxHeight: double.infinity,  
  child: Container(color: Colors.red, width: 4000, height: 50),  
);

屏幕強制OverflowBox和屏幕一個大小,而且OverflowBox讓它的子組件Container能夠有任意大小。

OverflowBoxUnconstrainedBox相似,不一樣的地方是,若是子組件比它大的話不會包warning。

在本例中Container的寬是4000,太大了。可是OverflowBox在這裏就不會像上例的UnconstrainedBox同樣報警。

示例 16

1_mpooMmLzFAQfKkQpuF_T_g.png

UnconstrainedBox(  
  child: Container(  
    color: Colors.red,  
    width: double.infinity,  
    height: 100,  
  )  
)

它不會繪製出任何的東西,只會在console裏報錯。

UnconstrainedBox讓它的子組件能夠擁有任意大小,然而它的子組件的寬是double.infinity

Flutter無法繪製無限寬的大小,因此它會拋出一個錯誤:BoxConstraints forces an infinite width

示例 17

1_UmwN0tqr7iDvsnNLr0tjtg.png

UnconstrainedBox(
  child: LimitedBox(
    maxWidth: 100,
    child: Container(
      color: Colors.red,
      width: double.infinity,
      height: 100,
    )
  )
)
示例 18

1_zu9CkTZLLcFEzErsxMVwzA.png

FittedBox(  
  child: Text('Some Example Text.'),  
)

屏幕強制FittedBox和屏幕一樣大小。Text會有本身的寬度(也叫作intrinsic寬度)。這個值依賴於字體和文字的多少等。

FittedBox會讓Text擁有任意的大小,可是Text把它本身的大小通知FittedBox以後,FittedBox會作縮放,直到填滿整個的寬度。

示例 19

1_VBIPl_EXOQVCx7LBCBsXDg.png

Center(  
  child: FittedBox(  
    child: Text('Some Example Text.'),  
  )  
)

可是,若是把FittedBox放在Center裏面的話會發生什麼呢?Center會讓FittedBox擁有任意它想要的大小。

FittedBox而後會把本身的大小縮放到Text的大小。由於FittedBoxText有一樣的大小,因此就不會有縮放的發生了。

示例 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裏,並且文字內容多到屏幕放不下的時候會發生什麼呢?

FittedBox會縮放到適應Text的大小,可是它不可能比屏幕還大。它會首先佔用屏幕的大小,而後對Text縮放到能夠顯示在屏幕裏。

示例 21

ex 21.png

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

ext22.png

FittedBox(  
  child: Container(  
    height: 20.0,  
    width: double.infinity,  
  )  
)

注意FittedBox只能縮放一個有邊界的組件(寬度或者高度都沒有無限值)。不然它不會繪任何的東西,只會在console裏顯示一條報錯信息。

示例 23

23.png

Row(  
  children:[  
    Container(color: Colors.red, child: Text('Hello!')),  
    Container(color: Colors.green, child: Text('Goodbye!)),  
  ]  
)

屏幕會強制Row使用屏幕的寬度。

就和UnconstrainedBox同樣,Row也不會對它的子組件施加任何的約束,而是容許他們有他們想要的任意大小。Row會把他們挨個放好,而後空出剩餘的空間。

示例 24

24.png

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並不會對它的子組件施加額外的約束,它的子組件太大無法徹底適應Row的寬度。這時候,就和UnconstrainedBox同樣顯示錯誤信息「overflow warning」。

示例 25

25.png

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包裹的話,那麼這個組件的寬度就不會再起做用了。它會使用Expanded的寬度。Expanded則強制原先的子組件使用Expanded的寬度。

總之一句話,只要你用了Expanded,那麼它子組件的寬度就無效了。

示例 26

26.png

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包裹,那麼每一個子組件所佔的比例是由flex參數決定的。每一個子組件租後都會接受Expanded的寬度。

示例 27

27.png

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代替了ExpandedFlexible會讓它的子組件有更小的寬度嗎?

FlexibleExpanded都會忽略子組件的大小。

也就是說在Row裏不可能根據子組件的大小按比例縮放子組件。Row要麼使用子組件的寬,要麼在你使用了Expanded或者Flexible的時候徹底忽略他們的寬度。

示例 28

1_V3mGIoK_py3zWf_eZkKxzg.png

屏幕強制Scaffold顯示到屏幕的大小。因此,Scaffold填充了屏幕。

Scaffold告訴Container它可使屏幕內的任意大小。

注意:當一個組件告訴它的子組件比某個特定的值小,咱們能夠認爲這個組件給它的子組件提供了「鬆散」的約束。後面會有更詳細講解。

示例 29

1_X_eWxGnCsvIkXlFBtLskyg.png

Scaffold(  
  body: **SizedBox.expand**(  
    child: Container(  
      color: blue,  
      child: Column(  
        children: \[  
          Text('Hello!'),  
          Text('Goodbye!'),  
      ],  
))))

若是咱們要Scaffold的子組件和它有相同的大小。咱們能夠把它的子組件放到SizedBox.expand

注意:當一個組件告訴它的子組件必須是某個特定的值,咱們能夠說這個組件給它的子組件提供了「緊」約束。

緊的和鬆散的約束

常常會聽到一種說法「緊」或者「鬆散」的約束,那麼他們到底指的是什麼呢?

一個約束只提供了一種可能,一個準確的值。也就是說,一個緊約束,它的最大寬和最小寬是同樣的,最大高和最小高也是同樣的。

若是你到Flutter的box.dart文件查找BoxConstraints構造函數,你會發現:

BoxConstraints.tight(Size size)  
    : minWidth = size.width,  
      maxWidth = size.width,  
      minHeight = size.height,  
      maxHeight = size.height;

若是你再看示例 2,屏幕強制紅色的Container顯示到屏幕的大小。屏幕能夠這麼作就是給Container傳入了約束。

一個鬆散的約束是指給子組件設定最大寬和高,可是讓子組件儘量的小。也就是說,一個鬆散約束有最小的寬和高,他們都等於0.

BoxConstraints.loose(Size size)  
    : minWidth = 0.0,  
      maxWidth = size.width,  
      minHeight = 0.0,  
      maxHeight = size.height;

示例 3中,Center讓紅色的Container不要比屏幕還大。Center就是給Container傳入了一個鬆散的約束。最後的效果是Center收到了父組件傳給它的緊約束,而給它的子組件Container傳入了鬆散約束。

從特定的組件學習佈局規則

知道了基本的佈局規則,也仍是不夠的。

每一個組件都有高度的自由決定如何使用基本佈局規則,因此若是隻是看到了組件名稱是沒法預測這個組件的佈局行爲的。

若是你去猜,基本都會錯。你不讀文檔或者組件的源代碼是沒法準確知道一個組件如何佈局的。

源代碼基本都很複雜,因此最好仍是看文檔。固然若是你想學習佈局的相關代碼,IDE的支持是足夠支持你的想法的。

這裏有個例子:

  • 找到Column,而後跳轉到它的源碼。最終你會到basic.dart文件。Column繼承自Flex,還能夠跳轉到Flex,它也在basic.dart文件裏。
  • 往下找到一個叫作createRenderObject的方法。這個方法返回RenderFlex。這就是Column對應的繪製對象。再跳轉到RenderFlex的源碼,你就會到flex.dart文件。
  • 往下找到~performLayout的方法,這個方法就是爲Column`佈局的方法。

1_t3WEi2V6BR3ydRIbacDK8w.png

很是感謝Simon Lightfoot的校驗,文章開頭的圖片和對文章內容的建議。

相關文章
相關標籤/搜索