Flutter你竟是這樣的佈局

xuyisheng發表於2020-06-28

對於Flutter學習者來說,掌握Flutter的佈局行為,直接決定了開發者在佈局的時候是否能做到高效、快速的開發,但是初學者面對茫茫多的Widget以及各種無法預料的佈局行為,總是很難將心中所想,轉化為Flutter的程式碼。

Flutter你竟是這樣的佈局
本文翻譯整理自https://flutter.dev/docs/development/ui/layout/constraints


順便插句話,我的開源專案Flutter_dojo,剛釋出了2.0,歡迎大家體驗。 Flutter_dojo

在這裡插入圖片描述
歡迎大家體驗。


當學習Flutter的人問你,為什麼寬度為100的某些小部件在顯示的時候,寬度不為100畫素時,你的預設答案是告訴他們將小部件放在Center內,對嗎?

不要這樣做。如果這樣做,他們會一次又一次地回來,詢問為什麼某些FittedBox不起作用,為什麼Column溢位了,或者IntrinsicWidth應該做什麼。

相反,請先告訴他們Flutter佈局與HTML佈局(可能是他們非常熟悉的)有很大不同,然後讓他們記住以下規則:

Constraints go down. Sizes go up. Parent sets position。

如果不瞭解此規則,就無法真正理解Flutter的佈局,因此Flutter開發人員應儘早學習。

更詳細地: Widget從其父級獲得自己的約束。約束只是一組4個雙精度數:

  • 最小和最大寬度
  • 最小和最大高度

然後Widget遍歷它的所有子Widget。Widget一個接一個地告訴其孩子約束(每個孩子可能有所不同),然後詢問每個孩子想要的大小,然後,Widget將其孩子定位(水平地在x軸上佈局,垂直地在y軸上佈局),最後,該小部件將其自身的大小告訴父級(當然,在原始約束內)。

例如,如果一個組合Widget包含帶有一些Padding和Column,並且希望如圖所示佈置其兩個Widget:

Flutter你竟是這樣的佈局

談判是這樣的:

  • Widget:嗨,Parent,我的約束是什麼?
  • Parent Widget:你的寬度必須在80到300畫素之間,而高度必須在30到85畫素之間。
  • Widget:嗯,因為我要有5畫素的Padding,所以我的子Widget最多可以有290畫素的寬度和75畫素的高度。
  • Widget:嗨,第一個子Widget,你的寬度必須在0到290畫素之間,並且必須在0到75高之間。
  • First child:好,那我希望寬290畫素,高20畫素。
  • Widget:嗯,由於我想將第二個子Widget放到第一個子Widget下面,所以第二個子Widget只剩下55畫素的高度。
  • Widget:嗨,第二個子Widget,你的高度必須在0到290之間,並且必須在0到55高之間。
  • Second child:好吧,我希望寬140畫素,高30畫素。
  • Widget:很好。我的第一個孩子的位置x:5和y:5,第二個孩子的位置x:80和y:25。
  • Widget:親愛的父母,我決定將尺寸設為300畫素寬,60畫素高。

Limitations

由於上述佈局規則,Flutter的佈局引擎具有一些重要限制:

  • Widget只能在其父級賦予的限制內決定其自身大小。這意味著Widget通常不能具有所需的任何大小。佈局是自上而下,當前widget會有基本的一些約束(來自它的父元素),主要是關於寬高的最小值和最大值
  • Widget無法知道也不決定其在螢幕上的位置,因為Widget的父級決定小部件的位置。它會依次詢問子元素關於佈局的基本限制要求,讓子元素上報期望的佈局結果,然後根據現狀和自己佈局演算法的特點,告訴子元素應該放到那兒,佔多大空間

由於父級的大小和位置又取決於其父級,因此在不考慮整個樹的情況下就無法精確定義任何小部件的大小和位置。

每個widget不一定會得到它期望的佈局大小,這方面顯著的例子是ConstrainedBox,很容易讓人困惑。 每個widget不能決定在螢幕中的位置,由父元素決定 因為這種佈局邏輯需要層層考慮上層元素,所以一個元素的最終佈局需要考慮整個UI裡widget樹。 如果為了精確區域性佈局,Container和ConstrainedBox會是一個可行的修飾佈局。

Examples

下面的29個示例,將演示Flutter的佈局思想。

github.com/marcglasber…

Example 1

在這裡插入圖片描述

Container(color: Colors.red)
複製程式碼

螢幕是Container的父級,它強制容器與螢幕的尺寸完全相同。 因此,容器將螢幕填滿並塗成紅色。

Example 2

Flutter你竟是這樣的佈局

Container(width: 100, height: 100, color: Colors.red)
複製程式碼

想要紅色的容器為100×100,但不是,因為螢幕會強制使其尺寸與螢幕完全相同。 因此,容器充滿了螢幕。

Example 3

Flutter你竟是這樣的佈局

Center(
   child: Container(width: 100, height: 100, color: Colors.red)
)
複製程式碼

螢幕會強制Center與螢幕完全相同,因此Center會填滿整個螢幕。 Center告訴Container它可以是所需的任何大小,但不能大於螢幕大小。 所以現在容器確實可以是100×100。

Example 4

Flutter你竟是這樣的佈局

Align(
   alignment: Alignment.bottomRight,
   child: Container(width: 100, height: 100, color: Colors.red),
)
複製程式碼

這與上一個示例不同,因為它使用Align而不是Center。 Align同樣告訴Container它可以是任何所需的大小,同時會在剩餘的可用空間中bottom-right對齊。

Example 5

Flutter你竟是這樣的佈局

Center(
   child: Container(
      color: Colors.red,
      width: double.infinity,
      height: double.infinity,
   )
)
複製程式碼

螢幕會強制Center與螢幕完全相同,因此Center會填滿整個螢幕。 Center告訴Container它可以是所需的任何大小,但不能大於螢幕大小。 容器希望具有無限大小,但由於不能大於螢幕,因此只能填充螢幕。

Example 6

Flutter你竟是這樣的佈局

Center(child: Container(color: Colors.red))
複製程式碼

螢幕會強制Center與螢幕完全相同,因此Center會填滿整個螢幕。 Center告訴Container它可以是所需的任何大小,但不能大於螢幕大小。 由於該Container沒有Child且沒有固定的大小,因此它決定要儘可能大,因此將其填滿整個螢幕。 但是Container為什麼要這樣決定呢?僅僅是因為這是建立Container的人的設計決定。 其它的Widget的建立方式可能有所不同,具體取決於情況。

Example 7

Flutter你竟是這樣的佈局

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。

Example 8

Flutter你竟是這樣的佈局

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與上一個示例中的大小相同。

Example 9

Flutter你竟是這樣的佈局

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也假定螢幕大小,從而忽略了其約束引數。

Example 10

Flutter你竟是這樣的佈局

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個畫素(最小)。

Example 11

Flutter你竟是這樣的佈局

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個(最大)。

Example 12

Flutter你竟是這樣的佈局

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

Example 13

Flutter你竟是這樣的佈局

UnconstrainedBox(
   child: Container(color: Colors.red, width: 20, height: 50),
)
複製程式碼

螢幕強制UnconstrainedBox與螢幕大小完全相同。 但是,UnconstrainedBox允許其子Container設定任意大小。

Example 14

Flutter你竟是這樣的佈局

UnconstrainedBox(
   child: Container(color: Colors.red, width: 4000, height: 50),
)
複製程式碼

螢幕強制UnconstrainedBox與螢幕大小完全相同,UnconstrainedBox將其子Container設為任意大小。 不幸的是,在這種情況下,容器的寬度為4000畫素,太大而無法容納在UnconstrainedBox中,因此UnconstrainedBox顯示溢位警告。

Example 15

Flutter你竟是這樣的佈局

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會盡可能地顯示儘可能多的內容,而不會發出警告。

Example 16

Flutter你竟是這樣的佈局

UnconstrainedBox(
   child: Container(
      color: Colors.red, 
      width: double.infinity, 
      height: 100,
   )
)
複製程式碼

你會在控制檯中看到錯誤。 UnconstrainedBox可以讓它的子Widget具有所需的任何大小,但是其子Widget是一個具有無限大小的Container。 Flutter無法呈現無限大小,因此會出現以下錯誤訊息:BoxConstraints forces an infinite width.

Example 17

Flutter你竟是這樣的佈局

UnconstrainedBox(
   child: LimitedBox(
      maxWidth: 100,
      child: Container( 
         color: Colors.red,
         width: double.infinity, 
         height: 100,
      )
   )
)
複製程式碼

這樣就不會再出現錯誤,因為當UnconstrainedBox為LimitedBox賦予無限大小時,它向下傳遞的約束為最大寬度是100畫素。 如果你將UnconstrainedBox替換為Center,則LimitedBox將不再應用其限制(因為其限制僅在獲得無限約束時才適用),並且容器的寬度允許超過100。 這解釋了LimitedBox和ConstrainedBox之間的區別。

Example 18

Flutter你竟是這樣的佈局

FittedBox(
   child: Text('Some Example Text.'),
)
複製程式碼

螢幕將強制FittedBox與螢幕完全相同。 文字將根據寬度調整自有的寬度屬性,字型屬性等。 FittedBox允許文字的尺寸為任意大小,但在將文字告知FittedBox大小後,FittedBox縮放文字直到填滿所有可用寬度。

Example 19

Flutter你竟是這樣的佈局

Center(
   child: FittedBox(
      child: Text('Some Example Text.'),
   )
)
複製程式碼

但是,如果將FittedBox放在Center內會怎樣?Center會將FittedBox設定為所需的任何大小,直至螢幕大小。 然後,將FittedBox調整為Text大小,並讓Text為所需的任何大小。 由於FittedBox和Text具有相同的大小,因此不會發生縮放。

Example 20

Flutter你竟是這樣的佈局

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會嘗試根據文字大小調整大小,但不能大於螢幕大小。然後假定螢幕大小,並調整文字的大小以使其也適合螢幕。

Example 21

Flutter你竟是這樣的佈局

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從螢幕上獲取其最大寬度,並在合適 的地方換行。

Example 22

Flutter你竟是這樣的佈局

FittedBox(
   child: Container(
      height: 20.0, 
      width: double.infinity,
   )
)
複製程式碼

FittedBox只能在有限制的寬高中進行Child的縮放(寬度和高度非無限大)。 否則,它將無法呈現任何內容,並且你會在控制檯中看到錯誤。

Example 23

Flutter你竟是這樣的佈局

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

螢幕強制行與螢幕大小完全相同。 就像UnconstrainedBox一樣,Row不會對其子代施加任何約束,而是讓它們成為所需的任意大小。Row然後將它們並排放置,任何多餘的空間都將保持空白。

Example 24

Flutter你竟是這樣的佈局

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會顯示溢位警告。

Example 25

Flutter你竟是這樣的佈局

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的寬度就變得無關緊要,並且會被忽略。

Example 26

Flutter你竟是這樣的佈局

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寬度。

Example 27

Flutter你竟是這樣的佈局

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的寬度。

Example 28

Flutter你竟是這樣的佈局

Scaffold(
   body: Container(
      color: blue,
      child: Column(
         children: [
            Text('Hello!'),
            Text('Goodbye!'),
         ]
      )))
複製程式碼

螢幕會強制設定Scaffold與螢幕大小完全相同,因此Scaffold會填滿螢幕。 Scaffold告訴容器它可以是所需的任何大小,但不能大於螢幕大小。

注意:當Widget告訴其子Widget它可以小於特定大小時,我們說該Widget為其Child提供了loose約束。

Example 29

Flutter你竟是這樣的佈局

Scaffold(
body: SizedBox.expand(
   child: Container(
      color: blue,
      child: Column(
         children: [
            Text('Hello!'),
            Text('Goodbye!'),
         ],
      ))))
複製程式碼

如果你希望Scaffold的子Widget與自己的Scaffold大小完全相同,則可以使用SizedBox.expand包裝其Child。

注意:當小部件告訴其子級必須具有一定大小時,我們說該小部件為其子級提供了tight約束。

Tight vs loose constraints

前面經常提到一些約束是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。

Learning the layout rules for specific widgets

知道一般的佈局規則是必要的,但這還不夠。 每個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感興趣的朋友可以加入我的Flutter修仙群。

在這裡插入圖片描述

相關文章