Flexbox Layout

UI Toolkit使用一個開源的Layout Engine: Yoga,Yoga是一個實作了Flexbox layout的HTML/CSS Layout system。

預設所有Visual Elements都是layout的一部份,在Layout中預設有以下行為

  • Container會將它的Children做垂直(vertically)分佈
  • Container矩形(rectangle)的位置包括其children rectangles
  • 在計算尺寸(size calculation)時,若這個visual element有Text的話,會以這個Text的大小來計算

Flexbox基本

  • Flex Item:在Flexbox中會有一個含有多個元素的Container稱為Flex Container,在這個Flex Container中的element稱為Flex Item

  • Flexbox的layout使用flex-flow directions的方式來做佈局,也就是說裡面的items會根據一個軸來從開頭到結尾做排列,如main axis的話從main-startmain-end排列;或cross axis的話從cross-startcross-end排列。

  • Main axis:由main-startmain-end的方向。

    • 預設是由左到右的方向與大部分語言文字方向相同的方向
    • 注意:Main axis不一定是由左到右的方向,它會根據flex-direction改變。
  • Cross axis:由cross-startcross-end的方向,也就是換行方向,如果Flex Item單行的數量多到超過Flex Container,需要換行時,預設會換到下一行的方向。

    • 預設是由上到下
  • Flex direction:設定main axis的方向,有以下方向可以設置

    • row (預設): 在LRT佈局中是從左到右;在RTL佈局中是從右到左
    • row-reverse: 與row相反,在LRT佈局中是從右到左;在RTL佈局中是從左到右
    • column: 方向是從上到下
    • column-reverse: 與column相反,從下到上
    • 以下是flex-direction為row的範例
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <style>
      .container {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      }
      .item{
      margin: 10px;
      width: 60px;
      height: 60px;
      background-color: blue;
      color: white;
      }
      </style>
      <div class="container1">
      <div class="item">item1</div>
      <div class="item">item2</div>
      <div class="item">item3</div>
      </div>
      item1
      item2
      item3

  • flex-wrap:設定Flex是否換行,預設會讓flex items保持在一行。

    • nowrap:所有flex items保持在一行
    • wrap:當flex items多到容不下時,會換到下一行,方向是從上到下
    • wrap-reversewrap的相反,當flex items多到容不下時,會換到上一行,方向是從下到上
    • 以下是flex-wrap的的範例

      nowrap

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container2-1 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width:200px;
      flex-wrap: nowrap;
      }
      </style>
      <div class="container2-1">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      wrap

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container2-2 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width:200px;
      flex-wrap: wrap;
      }
      </style>
      <div class="container2-2">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      wrap-reverse

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container2-3 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width:200px;
      flex-wrap: wrap-reverse;
      }
      </style>
      <div class="container2-3">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3



  • justify-content:定義Flex Items在Flex Container在main axis上空間分配的方式

    • flex-start:Items以flex-direction的開始爲方向排列。
    • flex-end:Items以flex-direction的尾部為方向排列。
    • center:Items在中心排列
    • space-between:Items在main axis上平均排列,但首尾Items貼邊。
    • space-around:Items在main axis上平均排列,Items之間使用相同的空間
    • 以下是Justify Content的例子

      flex-start

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <style>
      .container3-1 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      justify-content: flex-start;
      }
      </style>
      <div class="container3-1">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      flex-end

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <style>
      .container3-2 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      justify-content: flex-end;
      }
      </style>
      <div class="container3-2">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      center

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      .container3-3 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      justify-content: center;
      }
      <div class="container3-3">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      space-between

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      .container3-4 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      justify-content: space-between;
      }
      <div class="container3-4">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      space-around

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <style>
      .container3-5 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      justify-content: space-around;
      }
      </style>
      <div class="container3-5">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3



  • align-items:設定Flex Items對cross axis方向的排列方式

    • flex-start:Items會從cross axis的開始位置對齊
    • flex-end:Items會從cross axis的尾端位置對齊
    • stretch:當Items沒有設定高度時,Items會被拉伸填滿Container,(遵守最小寬度(min-width)/最大寬度(max-width))
    • center:Items會從cross axis的中心位置對齊
    • 以下是align-items的例子

      flex-start

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container4-1 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      height: 200px;
      align-items: flex-start;
      }
      </style>
      <div class="container4-1">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      flex-end

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container4-2 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      height: 200px;
      align-items: flex-end;
      }
      </style>
      <div class="container4-2">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      1
      2
      3

      stretch

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container4-3 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      height: 200px;
      align-items: stretch;
      }
      </style>
      <div class="container4-3">
      <div style="background-color:coral;min-height:30px;width:60px;">1</div>
      <div style="background-color:lightblue;min-height:50px;width:60px;">2</div>
      <div style="background-color:lightgreen;min-height:190px;width:60px;">3</div>
      </div>
      1
      2
      3

      center

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container4-4 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      height: 200px;
      align-items: center;
      }
      <div class="container4-4">
      <div class="item red">1</div>
      <div class="item green">2</div>
      <div class="item blue">3</div>
      </div>
      </style>
      1
      2
      3

  • align-self:與align-items類似,但是只會應用到該Item上

    • flex-start:Item會從cross axis的開始位置對齊
    • flex-end:Item會從cross axis的尾端位置對齊
    • stretch:Item會被拉伸填滿Container,(遵守最小寬度(min-width)/最大寬度(min-width))
    • center:Item會從cross axis的中心位置對齊
  • flex-grow:設定item在容器中佔多少空間,或是分到多少剩餘空間。

    • 以下舉例,容器的width為600px,紅色Item1的width為60px,綠色Item2的width為90px,藍色Item3的width為60px:
      沒有設定 flex-grow
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container5 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container5">
      <div class="red" style="width:60px;">1(60)</div>
      <div class="green" style="width:90px;">2(90)</div>
      <div class="blue" style="width:60px;">3(60)</div>
      </div>
      1(60)
      2(90)
      3(60)

      紅色Item的flex-grow:1,
      剩餘的空間為600-60-90-60=390,這會把剩餘的空間(390)全部分紅色Item
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container5 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container5">
      <div class="red item-a-5-1" style="width:60px;">1(60px+390px)</div>
      <div class="green" style="width:90px;">2(90px)</div>
      <div class="blue" style="width:60px;">3(60px)</div>
      </div>
      1(60px+390px)
      2(90px)
      3(60px)

      紅色Item的flex-grow:1,
      綠色Item的flex-grow:2,
      他們兩個會按比例分配剩餘空間390/3=130,紅色Item分配到130*1,綠色Item分到130*2
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container5 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container5">
      <div class="red item-a-5-1" style="width:60px;">1(60+130*1)</div>
      <div class="green item-a-5-2" style="width:90px;">2(90+130*2)</div>
      <div class="blue" style="width:60px;">3(60)</div>
      </div>
      1(60+130*1)
      2(90+130*2)
      3(60)

      flex-grow的數值也可以是小數,
      紅色Item的flex-grow:0.4,
      綠色Item的flex-grow:0.6,
      他們兩個按比例分配剩餘空間,紅色Item分配到390*0.4,綠色Item分到390*0.6
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container5 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container5">
      <div class="red" style="width:60px; flex-grow:0.4;">1(60+390*0.4)</div>
      <div class="green" style="width:90px; flex-grow:0.6;">2(90+390*0.6)</div>
      <div class="blue" style="width:60px;">3(60)</div>
      </div>
      1(60+390*0.4)
      2(90+390*0.6)
      3(60)

  • flex-shrink:在容器空間不足時,控制Item如何收縮

    • 此方式計算較複雜,除了flex-shrink設定比例,還需要根據容器中Item超出多少寬度,以及Item它本身的寬度來計算出各個Item收縮的比例。
    • 每個Item收縮的權重為其flex-shrink*寬度
    • 如果不希望被自動壓縮到,可以設定為0
    • 以下舉例,容器的width為600px,紅色Item1的width為300px,綠色Item2的width為300px,藍色Item3的width為300px,紅色Item的flex-shrink:1,綠色Item的flex-shrink:1,藍色Item的flex-shrink:2
      計算超出多少寬度:600(容器) - 300(紅色) - 300(綠色) - 300(藍色) = 300 (超出300)
      每個Item收縮的權重為其flex-shrink * 寬度:1*300 + 1*300 + 2*300 = 1200
      每個Item要收縮的寬度為:
      紅色:300(超出的寬度) * 1 (flex-shrink) * 300(Item的寬度) / 1200(權重) = 75
      綠色:300(超出的寬度) * 1 (flex-shrink) * 300(Item的寬度) / 1200(權重) = 75
      藍色:300(超出的寬度) * 2 (flex-shrink) * 300(Item的寬度) / 1200(權重) = 150
      因此三個元素最終的寬度為:
      紅色:300 - 75 = 225
      綠色:300 - 75 = 225
      藍色:300 - 150 = 150
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container6 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container6">
      <div class="red" style="width:300px; flex-shrink:1;">225</div>
      <div class="green" style="width:300px; flex-shrink:1;">225</div>
      <div class="blue" style="width:300px; flex-shrink:2;">150</div>
      </div>
      225
      225
      150

    • 再看另外一個例子:容器的width為600px,紅色Item的width為150px,綠色Item的width為300px,藍色Item的width為300px,紅色Item的flex-shrink:0,綠色Item的flex-shrink:2,藍色Item的flex-shrink:3
      計算超出多少寬度:600(容器) - 150(紅色) - 300(綠色) - 300(藍色) = 150 (超出150)
      每個Item收縮的權重為其flex-shrink * 寬度:0*150 + 2*300 + 3*300 = 1500
      每個Item要收縮的寬度為:
      紅色:150(超出的寬度) * 0 (flex-shrink) * 150(Item的寬度) / 1500(權重) = 0
      綠色:150(超出的寬度) * 2 (flex-shrink) * 300(Item的寬度) / 1500(權重) = 60
      藍色:150(超出的寬度) * 3 (flex-shrink) * 300(Item的寬度) / 1500(權重) = 90
      因此三個元素最終的寬度為:
      紅色:150 - 0 = 150
      綠色:300 - 60 = 240
      藍色:300 - 90 = 210
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container6 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container6 width:600px">
      <div class="red" style="width:150px; flex-shrink:0;">150</div>
      <div class="green" style="width:300px; flex-shrink:2;">240</div>
      <div class="blue" style="width:300px; flex-shrink:3;">210</div>
      </div>
      150
      240
      210

    • flex-shrink也可以是小數:容器的width為600px,紅色Item的width為300px,綠色Item的width為300px,藍色Item的width為300px,紅色Item的flex-shrink:0.1,綠色Item的flex-shrink:0.4,藍色Item的flex-shrink:0.5
      計算超出多少寬度:600(容器) - 300(紅色) - 300(綠色) - 300(藍色) = 300 (超出150)
      每個Item收縮的權重為其flex-shrink * 寬度:0.1*300 + 0.4*300 + 0.5*300 = 300
      每個Item要收縮的寬度為:
      紅色:300(超出的寬度) * 0.1 (flex-shrink) * 300(Item的寬度) / 300(權重) = 30
      綠色:300(超出的寬度) * 0.4 (flex-shrink) * 300(Item的寬度) / 300(權重) = 120
      藍色:300(超出的寬度) * 0.5 (flex-shrink) * 300(Item的寬度) / 300(權重) = 150
      因此三個元素最終的寬度為:
      紅色:300 - 30 = 270
      綠色:300 - 120 = 180
      藍色:300 - 150 = 150
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container6 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container6 width:600px">
      <div class="red" style="width:300px; flex-shrink:0.1;">270</div>
      <div class="green" style="width:300px; flex-shrink:0.4;">180</div>
      <div class="blue" style="width:300px; flex-shrink:0.5;">150</div>
      </div>
      270
      180
      150


  • flex-basis:設定Item佔用空間。如果沒設定或設為auto則Item的值為width(flex-direction:row)或是height(flex-direction:column)的值。

    • 以下是範例:
      紅色Item的style="width:300px; flex-basis:auto;"。
      綠色Item的style="width:300px; flex-basis:100px;"。
      藍色Item的style="width:300px;"。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <style>
      .container6 {
      display: flex;
      flex-direction: row;
      background-color: yellow;
      width: 600px;
      color: white;
      }
      </style>
      <div class="container6 width:600px">
      <div class="red" style="width:300px; flex-basis:auto;">300</div>
      <div class="green" style="width:300px; flex-basis:100px;">100</div>
      <div class="blue" style="width:300px;">300</div>
      </div>
      300
      100
      300


Reference: https://docs.unity3d.com/2020.1/Documentation/Manual/UIE-LayoutEngine.html
https://discussions.unity.com/t/ui-toolkit-introduction-and-flexbox-layout/316856
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
https://github.com/xieranmaya/blog/issues/9

評論