iOS 上的 FlexBox 佈局 為什麼要了解 FlexBox?
最近時不時的聽到關於 FlexBox 的聲音,除了在 Weex 以及 React Native 兩個著名的跨平臺專案裡有用到 FlexBox 外,AsyncDisplayKit 也同樣引入了 FlexBox 。
先說說 iOS 本身提供給我們 2 種佈局方式:
-
Frame,直接設定橫縱座標,並指定寬高。
-
Auto Layout,通過設定相對位置的約束進行佈局。
Frame 沒什麼太多可說的了,直接制定座標和大小,設定絕對值。
Auto Layout 本身用意是好的,試圖讓我們從 Frame 中解放出來,擺脫關於座標和大小的刻板思考方式。轉而利用 UI 之間的相對位置關係,設定對應約束進行佈局。
但是 Auto Layout 好心並未做成好事,它的語法又臭又長! 至今學習 iOS 兩年,我使用到原生 Auto Layout 語法的時候屈指可數。只能靠 Masonry 這樣的第三方庫來使用它。
Auto Layout 的原理
說完了 Auto Layout 的使用,再來看看它工作原理。
實際上,我們設定 Auto Layout 的約束,就構成一系列的條件,成為一個方程。然後解出 Frame 的座標和大小。
例如,我們設定一個名為 A 的 UI :
A.center = super.center
A.width = 40
A.height = 40
則:A.frame = (super.center.x,super.center.y,40,40)
再設定一個 B:
B.width = A.width
B.height = A.height
B.top = A.bottom + 50
B.left = A.left
則:B.frame = ( A.x , A.y + A.height + 50 , A.width , A.height )
如圖:
Cassowary
Auto Layout 內部有專門用來處理約束關係的演算法,我一直以為是蘋果自家研發的,查閱資料才發現是來自一個叫 Cassowary 的演算法。
Cassowary是個解析工具包,能夠有效解析線性等式系統和線性不等式系統,使用者的介面中總是會出現不等關係和相等關係,Cassowary開發了一種規則系統可以通過約束來描述檢視間關係。約束就是規則,能夠表示出一個檢視相對於另一個檢視的位置。
有興趣的可以進一步瞭解該演算法的實現。
Frame / Auto Layout / FlexBox 的效能對比
在對 Auto Layout 進行一番瞭解之後,我們很容易得出 Auto Layout 因為多餘的計算,效能差於 Frame 的結論。
但究竟差多少呢?FlexBox 的表現又如何呢?
這裡根據 從 Auto Layout 的佈局演算法談效能 裡的測試程式碼進行修改,對 Frame / Auto Layout / FlexBox 進行佈局,分段測算 10 ~ 350 個 UIView 的佈局時間。取 100 次佈局時間的平均值作為結果,耗時單位為秒。
結果如下圖:
雖然測試結果難免有偏差,但是根據折線圖可以明顯發現,FlexBox 的佈局效能是比較接近 Frame 的。
60 FPS 作為一個 iOS 流暢度的黃金標準,要求佈局在 0.0166667 s 內完成,Auto Layout 在超過 50 個檢視的時候,可能保持流暢就會開始有問題了。
本次測試使用的機器配置如下:
採用 Xcode9.2 ,iPad Pro (12.9-inch)(2nd generation) 模擬器。
測試佈局的專案程式碼上傳在 GitHub
FlexBox 是什麼?
FlexBox 是一種 UI 佈局方式,並得到了所有瀏覽器的支援。FlexBox 首先是基於 盒裝狀型 的,Flexible 意味著彈性,使其能適應不同螢幕,補充盒狀模型的靈活性。
FlexBox 把每個檢視,都看作一個矩形盒子,擁有內外邊距,沿著主軸方向排列,並且,同級的檢視之間沒有依賴。
和 Auto Layout 類似,FlexBox 採用了描述性的語言去進行佈局,而不像 Frame 直接用絕對值座標進行佈局。
彈性佈局的主要思想是讓 Flex Container 有能力來改變 Flex Item 的寬度和高度,以填滿可用空間(主要是為了容納所有型別的顯示裝置和螢幕尺寸)的能力。
最重要的是, FlexBox 佈局與方向無關,常規的佈局設計缺乏靈活性,無法支援大型和複雜的應用程式(特別是涉及到方向轉變,縮放、拉伸和收縮等)。
FlexBox 組成
採用 FlexBox 佈局的元素,稱為 Flex Container。
Flex Container 的所有子元素,稱為 Flex Item。
下面會講一下 FlexBox 裡面的一些概念,方便之後進行 FlexBox 的使用。
Flex Container
前面提到了,FlexBox 的一個特點,就是檢視之間,是沒有依賴的。
Flex Item 的排布,就依賴於 Flex Container 的屬性設定,而不用相互之間進行設定。
所以先說一下 Flex Containner 的屬性設定。
Flex Direction
FlexBox 有一個 主軸(main axis) 和 側軸(cross axis)的概念。側軸垂直於主軸。
它們可以是水平,也可以是垂直。
主軸預設為 Row , 側軸預設為 Column:
Flex Direction 決定了 Flex Containner 內的主軸排布方向。
主軸預設為 Row (從左到右):
同時,也可以設定 RowRevers(從右至左):
Column(從上到下):
ColumnRevers(從下到上):
Flex Wrap
Flex Wrap 決定在軸線上排列不下時,檢視的換行方式。
Flex Wrap 預設設定為 NoWrap,不會換行,一直沿著主軸排列到螢幕之外:
設定為 Wrap ,則空間不足時,自動換行:
設定 WrapReverse,則換行方向與 Wrap 相反:
這是一個非常有用的屬性。比如典型的九宮格佈局,iOS 如果不是用 UICollectionView 做,那麼就需要儲存 9 個例項,然後做判斷,計算 frame ,可維護性實在不高。使用UICollectionView 可以很好的解決佈局,但很多場景並不能複用,做起來也不是特別簡單。
FlexBox 佈局的話,用 Flex Wrap 屬性設定 Wrap 就可以直接搞定。
移動平臺上相似的方案,比如 Android 的 Linear Layout 和 iOS 的 UIStackView ,但卻遠沒有 FlexBox 強大。
Display
Display 選擇是否計算它,預設為 Flex. 如果設定為 None 自動忽略該檢視的計算。
在根據邏輯顯示 UI 時,比較有用。
比如我們現有的業務,需要顯示的騰訊身份標示。按照一般做法,多個 icon 互相連成一排,根據身份去設定不同的距離,同時隱藏其他 icon ,比較的麻煩。iOS 最好的辦法是使用 UIStackView ,這又有版本相容等問題。而使用 FlexBox 佈局,當不是某個身份時,只要設定 Display 為 None,就不會被納入 UI 計算當中。
Justify Content
Justify Content 用於定義 Flex Item 在主軸上的對齊方式:FlexStart(主軸起點對齊),FlexEnd(主軸終點對齊),Center(居中對齊)。
還有SpaceBetween(兩端對齊):
設定兩端對齊,讓 Flex Item 之間的間隔相等。
SpaceAround(外邊距相等排列):
讓每個 Flex Item 四周的外邊距相等
Align Items
Align Items 定義 Flex Item 在側軸上的對齊方式。
Align Items 可以和主軸對齊方式 Justify Content 一樣,設定FlexStart ,FlexEnd,Center,SpaceBetween,SpaceAround 。
Align Items 還可以設定 Baseline(基線對齊):
如圖所示,它是基於 Flex Item 的第一行文字的基線對齊。
如果 Baseline 和 Flex Item 的行內軸與側軸為同一條,則該值與 FlexStart 等效。 其它情況下,該值將參與基線對齊。
Align Items 還可以設定為 Stretch:
Stretch 讓 Flex Item 拉伸填充整個Flex Container。Stretch會使Flex Item的外邊距在遵照對應屬性限制下,儘可能接近所在行或列的尺寸。
如果 Flex Item 未設定數值,或設為 auto,將佔滿整個Flex Container的高度
Align Content
Align Content 也是側軸在 Flex Item 裡的對齊方式,只不過是以一整個行,作為最小單位。
注意,如果Flex Item只有一根軸線(只有一行的Flex Itme),該屬性不起作用。
調整為 FlexWrap 為 Wrap,效果才顯示出來:
Flex Item
在上面說完了 Flex Container 的屬性,終於說到了 Flex Item. Flex Container 裡的屬性,都是作用於自己包含的 Flex Item,Flex Item 的屬性,都是作用於自己本身。
AlignSelf
AlignSelf 可以讓單個 Flex Item 與其它 Flex Item 有不一樣的對齊方式,覆蓋 Align Items屬性。
預設值為auto,表示繼承Flex Container的Align Items屬性。如果它本身沒有Flex Container,則等同於Stretch。
FlexGrow
FlexGrow 可以設定分配剩餘空間的比例。即如何擴大。
FlexGrow 預設值為 0,如果沒有去定義 FlexGrow,該佈局是不會擁有分配剩餘空間權利的。
例如:
整體寬度 100 , sub1 寬為 10 ,sub2 寬為 20 ,則剩餘空間為 70。
設定 FlexGrow 就是分配這 70 寬度的比例。