Codelab: Flutter 佈局基礎教程

請注意,本文件已在官方文件中被移除,新的頁面正在翻譯中,本頁面作為臨時替代頁面,將在新頁面翻譯結束之後刪除,請勿在站外引用或分享以避免不好的使用者體驗,謝謝!

Row and Column 是 Flutter 世界非常重要的兩個 widgets。想要把一個帶 label 的 Text widget 放到另一個具有相應值的 Text widget邊上?使用一個 Row。想要現實多對 labels 和值?使用一個包含多個 RowColumn。包含多個欄位的表單,旁邊有圖示的選單選項,旁邊有搜尋欄的按鈕,這些都是要用到 Rows 和 Column 的地方。

codelab 將帶你瞭解 Rows and Column 如何工作。因為它們非常相似,一旦你學會瞭如何使用 Row,codelab 將會向你展示如何把相同的概念應用於 Column。使用內建的編輯器,你將會邊玩邊測試你學到的知識。

從一個 Row 和一些 children開始

Row or Column 的主要功能就是包含其他的 widgets,這些 widgets 被稱為 children。在一個 Row 裡,所有的 children 都會根據 text 方向從頭到尾水平排列。如果你的裝置被設定為英文或其他從左到右的語言,就會從左開始,如果你使用阿拉伯語或者其他從右到左顯示的語言,就會從右到左排列。

程式碼例子

下面是一個叫作 MyWidget 的 widget,在其內部建立了一個 Row,然後請試著將三個 BlueBox widgets 加到 Row 的 children中。

主軸空間

Row 的主軸是指水平方向的軸(Column 的主軸是指豎直方向的)。每一個 Row 都有一個叫 mainAxisSize 的屬性,它決定了此 Row 沿著水平方向佔用空間的大小。預設情況下,mainAxisSize 的值為 MainAxisSize.max,這意味著 Row 將會佔用所有可用的水平方向空間。你可以使用 MainAxisSize.min 實現讓一個 Row 佔用儘可能少的空間。

程式碼例子

這裡的例子是你剛剛完成的。試著將 RowmainAxisSize 的值設為 MainAxisSize.min,看看會發生什麼。

主軸對齊

如果你將一個 RowmainAxisSize 設為最小值,在 children 之外就不會有更多的空間。如果你將其設為最大值,Row 就會有多出來的空間。你可以使用 mainAxisAlignment 屬性來控制 Row 中的 children 對齊的方式。

MainAxisAlignment 有六種不同的列舉值:

  • 將所有的 children 儘可能向 Row 的 start 方向排列(如果是從左到右,那就是靠左排列)。

  • 將所有的 children 儘可能向 Row 的 end 方向排列。

  • 將 children 聚在 Row 主軸的中間位置。

  • 將主軸空白位置進行均分,用來在 children 之間製造間隔,首尾 children 距邊緣沒有間隙。

  • 很像 spaceBetween,除了讓首尾 children 距邊緣也有相同的間隙。

  • 很像 spaceEvenly,只是首尾 children 距邊緣間距為中間 children 間距的一半。

程式碼例子

下面的 row 的 mainAxisAlignment 被設為了 start。試著將其改為其他的值,然後重新執行看看會怎麼樣。

交叉軸對齊

Row widgets 的交叉軸是豎直方向的軸,你可以用 crossAxisAlignment 屬性來控制 children 如何在垂直方向排列。預設值是 CrossAxisAlignment.center,一共有五種值:

  • 將所有的 children 向 Row 豎直方向的 start 方向排列(如果是從上到下,你可以修改 verticalDirection 來改變)。

  • 將所有的 children 向 Row 豎直方向的 end 方向排列(預設是底部)。

  • 將 children 聚在 Row 豎直方向軸的中間位置。

  • 所有的 Children 的高度會被拉伸到和 Row 一樣,填滿豎直方向軸的空間。

  • 所有的 Children 的 baselines 在豎直方向對齊。

程式碼例子

Row 有兩個小的 children 和一個大的。crossAxisAlignment 屬性預設為 center。可以試著將其變為其他值然後重新執行,看看會怎樣。

會有一個警告: CrossAxisAlignment.baseline requires that another property be set as well, so you will see an error if you try that one. 不用擔心,在下一節將會對此進行討論。

基線對齊

Sometimes it’s handy to align widgets containing text not by their overall bounds, but by the baselines used by their characters. That’s what CrossAxisAlignment.baseline is for. You can use it in combination with a Row’s textBaseline property (which indicates which baseline to use) to align a Row’s children along their baselines.

有時候根據包含文字的 widgets 的基線對齊是比較方便的,而不是根據它們的整體邊框對齊。那就是 CrossAxisAlignment.baseline 的用途。你可以使用聯合使用 RowtextBaseline屬性(決定按照哪種基線來對齊),來決定 Row 的所有 children 根據基線對齊。

程式碼例子

row 裡包含三個擁有不同字型大小的 Text widgets。試著將 crossAxisAlignment 屬性設為 baseline,然後試驗 textBaseline 的不同值(TextBaseline 列舉值裡包含可用的 baseline 值)。

可伸縮 children

到目前為止,例子中所有用作 children 的 widgets 都有一個固定的大小。不過 Row 可以讓它的 children 可伸縮,來適應可用的空間。為了更好的理解這是怎麼回事兒,最好看看 Row 的大小和它的 children。

  1. 首先,Row 首先會要求它所有的 children 想要多大的尺寸。

  2. 然後,它會計算主軸(水平)的剩餘空間。

  3. 然後它把剩下的空間根據 children 的 flex 值分給它的可伸縮的 children,這些可伸縮的 children 可以使用他們提供的部分或者全部的空間。

  4. 在那時, Row 知道所有的 children 的尺寸有多大,然後可以根據你之前學到的 axis size 和 alignment 屬性來排列它們。

大多數 widgets 是固定大小的。你可以將他們包裹在一個 Flexible widget 中來將它們變為可伸縮的。 Flexibles 有兩個重要屬性: flex 值決定與其他 children 相比可佔用剩餘空間的多少, fit 屬性決定其 child 是否佔用所有額外的空間。

程式碼例子

試著將 row 中間的 box 包裹在一個 flex factor 為 1 並且 fitFlexFit.loose 的 Flexible widget中。然後試著將 fit 改為 FlexFit.tight`,看看會發生什麼。

flex factor 為 1 和 fitFlexFit.tight 的組合是非常常見的, 更簡單的方式是直接使用 Expanded widget.

Flex factors

如果 RowColumn 中多個 children 都是可伸縮的,那麼如何分配可用空間取決於它們的 flex 值。每個 child 獲得的空間將取決於他們的 flex 值佔所有 children 的 flex 值之和的比例。

remainingSpace * (flex / totalOfAllFlexValues)

例如,如果有兩個 flex 值為 1 的 children,每個將獲得一半的可用空間。如果有兩個 flex 值為 1 的 children,還有一個 flex 值為 2 的 child,那麼前兩個 children 將各獲得四分之一的可用空間,另一個 child 將獲得一半的可用空間。

程式碼例子

在這個例子中, Row 的所有三個 children 都是可伸縮的,試著改變它們的 flex 值然後重新執行看看它們的尺寸如何改變。

如果沒有空間了怎麼辦?

正如你所看到的,當一個 Row 問它其中一個可伸縮的 child 想要多大空間時,它會根據這個 child 的 flex 值分配給它一個最大值。但是固定大小的 children 沒有這個限制,它們可以自己決定大小。

一個副作用就是,無法阻止一個固定大小的 child 宣告超出 Row 所能支援的大小。當這種情況發生時,就會發生溢位。你可以透過修改這個 child 的大小或者使用一個可捲動的 widget 來解決這個問題。

程式碼例子

下面的 Row 包含一個特別寬的 widget。執行程式碼看會發生什麼,然後試著修改Container的寬度使其適應。

試著使用 SizedBox 來留出空間

如果你需要在一個 Row 中的兩個 children 之間指定一個特定的間隔,一個簡單的方法是在中間放一個寬度合適的 SizedBox

程式碼例子

試著用一個寬度 100 的 SizedBox 在兩個 items 中間製造一些間隔。

Spacers 留出可變空間

使用Spacers 是另一個在 Row 的 children 之間留出空間的方法。它們是可伸縮的,可以填滿任何剩下的空間。

程式碼例子

試著在第一個和第二個 children 之間加一個 Spacer

等等, 我不是還要學習 Columns 嗎?

給你個驚喜,你已經學習了。Row 的所有用法和 Column 是一樣的,只是維度不同。 Row 的主軸是水平的,而 Column 的主軸是豎直的,但是它們設定其 children 的大小和位置的方式是一樣的。它們還共用一個基本類 Flex。所以你已經學習的有關 Row 的用法,同樣適用於 Column

程式碼例子

這裡有一個包含不同尺寸和一些重要屬性已經設定好的 children 的 Column。試著擺弄以下,你會發現 Column 就像一個豎過來的的 Row

將它們放在一起

現在你已經熟悉了 RowColumn 的重要屬性,你已經可以來聯絡將它門組合在一起來建立使用者介面。下面的例子將帶你完成一個名片顯示的建立。

程式碼例子

每一張名片都需要一個名字和頭銜,讓我們從這裡開始。

  • 新增一個 Column widget

  • 新增兩個 text widgets 到 Column 的 children 清單中:

    • 第一個是名字(簡短一點更適合於一個小視窗),使用 headline 樣式:

style: Theme.of(context).textTheme.headline
  • 第二個 text widget 應該是 Experienced App Developer,使用預設樣式(不用設定 style 屬性)。

  • 設定 ColumncrossAxisAlignment 為 start,使得 text widgets 會開始對齊,而不是居中。

  • ColumnmainAxisSize 設為 MainAxisSize.min,這樣 card 才不會擴充套件到整個 window 那麼高。

名片的左上角通常會有一個圖示或者標誌,所以下一步是加一個到你的名片上。將你剛建立的 Column 包裹在一個 Row widget 中。

Row(
  children: [
    Column(  ), // <- This should be the Column you made in the previous step
  ],
);

現在你可以新增一個圖示:

  • 在你的 RowColumn 的前面,加一個 Padding widget。

    • 設定 paddingconst EdgeInsets.all(8)

    • 將一個 Icon widget 作為 Padding widget 的 child。

      • 你可以使用任何 icon resource, Icons.account_circle看起來就不錯。

      • Icon 的大小設定為 50。

你的第一個 Row 現在完成了。還有兩件事要做,你需要一個 Column 把它們放進去。把你的 Row 包裹進一個 Column widget 就像這樣:

 Column(
   children: [
     Row(  ), // <- This should be the Row with your Icon and Text widgets.
   ],
 );

然後按照以下步驟完成你的新 Column

  • 設定 Column 的 mainAxisSize 為最小

    • 否則它會充滿整個螢幕!

  • 設定ColumncrossAxisAlignment 為 stretch。

    • 這使得所有 children 都會拉伸到最大寬度

  • Column 中你的 Row 下面新增更多的 widgets:

    • 一個高度為 8 的 SizedBox

    • 一個空 Row (沒有 children 或其他屬性)

    • 一個高度為 16 的 SizedBox

    • 另一個空 Row

現在就差幾步了。接下來是第二個 Row。新增以下的 widgets 作為它的 children:

  • 一個地址為 ‘123 Main Street’ 的 Text widget

  • 一個電話為 ‘800-123-1234’ 的 Text widget

如果你現在執行程式碼,你會看到這兩個 Textwidgets 是挨著的,而不是在 Row 的兩端對齊,這是不對的。你可以將 RowmainAxisAlignment 設為 spaceBetween,使得這兩個 Text widge 中間有些間隔。

最後一步是在名片的底部放一些圖示。

  • 新增四個 Icon widgets 到最後一個 Row 中。你可以使用任何你喜歡的圖示資源,但是以下圖示是一個很好的選擇,用來展示你想象中的關注於 accessibility, fast development, and multi-platform apps 的開發人員:

    • Icons.accessibility
    • Icons.timer
    • Icons.phone_android
    • Icons.phone_iphone
  • 設定 RowmainAxisAlignment 屬性為 MainAxisAlignment.spaceAround