iOS的MyLayout布局系列-流式布局MyFlowLayout

iOS布局體系的概覽

? ? ?在我的CSDN博客中的幾篇文章分別介紹MyLayout布局體系中的視圖從一個方向依次排列的線性布局(MyLinearLayout)、視圖層疊且停靠于父布局視圖某個位置的框架布局(MyFrameLayout)、視圖之間通過約束和依賴實現(xiàn)布局的相對布局(MyRelativeLayout)、以及多列多行排列的表格布局(MyTableLayout)、以及本文將要介紹的流式布局(MyFlowLayout)這5種布局體系。這些視圖布局的方式都有一些統(tǒng)一的特征,都要求必須將子視圖放入到一個特殊的視圖中去,我們稱這些特殊的視圖為布局視圖(Layout View)。這些布局視圖都有一個共同的基類:基礎(chǔ)布局視圖(MyBaseLayout)。同時我們還為視圖建立了很多擴展的屬性來進行位置和尺寸的設(shè)置,以及我們還專門建立了服務(wù)某些布局視圖的視圖擴展屬性。在這些擴展屬性中:用于定位視圖位置的類是MyLayoutPos類,這個類可以用來決定視圖的上、下、左、右、水平居中、垂直居中六個方位的具體值;而用于決定視圖尺寸的類是MyLayoutSize類,這個類可以用來決定視圖的高度和寬度的具體值;用于決定視圖排列布局方向的是枚舉MyLayoutViewOrientation類型,方位類型定義了垂直和水平兩個方位;用于決定視圖停靠區(qū)域的MyGravity枚舉類型,枚舉類型定義了14種停靠的區(qū)域類型,這里要分清楚的是MyGravity和MyLayoutPos的區(qū)別,前者是用來描述某個具體的方位,而后者則是用來某個方位的具體位置;用于描述子視圖和布局視圖四周內(nèi)邊距的padding屬性,這個屬性只用于布局視圖;用于描述布局視圖的尺寸大小由子視圖整體包裹的wrapContentWidth,wrapContentHeight的屬性;用于描述蘋果各種屏幕尺寸適配的MySizeClass定義,以及具體的實現(xiàn)類MyLayoutSizeClass類。這些屬性和類共同構(gòu)建了出了一套完整的iOS界面布局系統(tǒng)。下面是這個套界面布局體系的類結(jié)構(gòu)圖:


介紹完布局的整個體系結(jié)構(gòu)后,我們先來理解的概念,所謂流就是向某個方向依次的排列,而當(dāng)?shù)竭_(dá)某個設(shè)定的邊界或者設(shè)定的數(shù)量時則另起一行并回到原先的起點重新開始繼續(xù)按某個方向依次排列。一個最簡單的例子就是假設(shè)我們在寫文章時,假定每行的文字規(guī)定了80個字則我們首先在第一行書寫文字,而當(dāng)要書寫的文字超過80個字時我們就會自動另起一行重新開始。因此我們可以看出流布局有如下的特點:

1.總是優(yōu)先沿著一個固定的方向排列,其中沿著的方向一共有兩種: 從先左到右,然后從上到下;或者先從上到下,然后從左到右。我們稱先從左到右然后從上到下的流為垂直流,也稱為縱向流;而先從上到下然后從左到右的流為水平流,也稱為橫向流。

2.當(dāng)流沿著某個特定方向滿足了某個特定的要求后才會進行換行重新開始排列,而這個特定的要求有兩種:一種是容器空間不足以容納要排列的內(nèi)容,一種是內(nèi)容到達(dá)了容器空間的某個特定方向的數(shù)量限制。前者的一個具體的實例就是WEB頁面中CSS中所定義的float布局,或者一些標(biāo)簽流;而后者的一個具體實例就是微信或者支付寶里面的錢包功能菜單列表

下面我們就列出一些典型的流式布局:


MyFlowLayout就是一個用來實現(xiàn)上述流功能的布局視圖類,他是從MyBaseLayout類派生。支持分別從垂直和水平兩個方向的進行布局,同時支持子視圖按內(nèi)容填充約束或者填充數(shù)量約束兩種換行或者換列策略的四種布局:

1.垂直內(nèi)容填充約束布局。

這種流式布局的布局機制是,里面的子視圖按添加的順序每行依次從左排列到右,而當(dāng)布局視圖的剩余寬度容納不下一個要插入的新的子視圖的寬度時則會新起一行,重新從左到右繼續(xù)排列,如果遇到某個子視圖的寬度甚至比布局視圖還要寬時則總時會壓縮子視圖的寬度和布局視圖的寬度保持一致,這樣最終形成的結(jié)果是子視圖將按從左到右,從上到下的順序依次排列,且每行的數(shù)量不固定。下面就是這種流式布局的效果圖:


2.垂直數(shù)量約束布局。

這種流式布局的布局機制是,里面的子視圖按添加的順序每行依次從左排列到右,當(dāng)一行內(nèi)的子視圖的數(shù)量到達(dá)布局視圖約定的數(shù)量值時則會新起一行,重新從左到右繼續(xù)排列,這樣最終形成的結(jié)果是子視圖將按從左到右,從上到下的順序依次排列,且每行的數(shù)量是固定的。下面就是這種流式布局的效果圖:


3.水平內(nèi)容填充約束布局。

這種流式布局的布局機制是,里面的子視圖按添加的順序每列依次從上排列到下,而當(dāng)布局視圖的剩余高度容納不下一個要插入的新的子視圖的高度時則會新起一列,重新從上到下繼續(xù)排列,如果遇到某個子視圖的高度甚至比布局視圖還要高時則總時會壓縮子視圖的高度和布局視圖的高度保持一致,這樣最終形成的結(jié)果是子視圖將按從上到下,從左到右的順序依次排列,且每列的數(shù)量不固定。下面就是這種流式布局的效果圖:


4.水平數(shù)量約束布局。

這種流式布局的布局機制是,里面的子視圖按添加的順序每列依次從上排列到下,當(dāng)一列內(nèi)的子視圖的數(shù)量到達(dá)布局視圖約定的數(shù)量值時則會新起一列,重新從上到下繼續(xù)排列,這樣最終形成的結(jié)果是子視圖將按從上到下,從左到右的順序依次排列,且每列的數(shù)量是固定的。下面就是這種流式布局的效果圖:


MyFlowLayout流式布局類

一、流式布局的建立以及類型的指定

## MyFlowLayout要實現(xiàn)上面四種不同的布局可以通過初始化方法:

-(id)initWithOrientation:(MyLayoutViewOrientation)orientation arrangedCount:(NSInteger)arrangedCount;

+(id)flowLayoutWithOrientation:(MyLayoutViewOrientation)orientation arrangedCount:(NSInteger)arrangedCount;

中的orientation參數(shù)來指定流式布局的方向,當(dāng)設(shè)置為MyLayoutViewOrientation_Vert時表示垂直流式布局,而當(dāng)設(shè)置為MyLayoutViewOrientation_Horz時則表示為水平流式布局;而其中的arrangedCount參數(shù)則是指定布局方向排列的子視圖的數(shù)量約束值,當(dāng)設(shè)置為0時則表示建立的不是數(shù)量約束布局而是內(nèi)容填充約束布局。如果我們調(diào)用init方法來初始化一個流式布局的話則默認(rèn)建立的是一個垂直內(nèi)容填充約束布局。另外我們也可以通過類的兩個屬性:

@property(nonatomic,assign)MyLayoutViewOrientation orientation;

@property(nonatomic,assign) NSInteger arrangedCount;

來隨時調(diào)整流式布局的方向和數(shù)量的,也就是說流式布局的種類是隨時都可以變換的。

二、流式布局對包裹特性的支持

因為MyFlowLayout是繼承自MyBaseLayout因此MyFlowLayout類的高度和寬度尺寸也支持由包裹的子視圖來決定,也就是支持wrapContentWidth和wrapContentHeight兩個屬性設(shè)置為YES的情況,但不是4種流式布局都支持包裹屬性,對于數(shù)量約束布局來說不管是水平的還是垂直都支持包裹屬性,而對于內(nèi)容填充約束布局來說則當(dāng)是垂直布局時只支持wrapContentHeight為YES的情況,因為每行能填充的子視圖的數(shù)量是依賴于布局視圖的寬度決定的,因此是不能支持wrapContentWidth為YES的場景的;同樣的道理對于水平內(nèi)容約束布局來說只支持wrapContentWidth為YES則不支持wrapContentHeight為YES的場景的。

三、流式布局內(nèi)子視圖的尺寸位置和間距

對于流式布局來說,雖然我們總是按約定的規(guī)則來排列定位其中的每個子視圖的位置,但是我們依然在某種情況下需要設(shè)置每個子視圖之間的間距,以及子視圖本身的高度和寬度尺寸。我們可以通過設(shè)置子視圖的frame值來指定每個子視圖的高度或者寬度,我們也可以通過視圖擴展的屬性widthSize,heightSize,myWidth,myHeight,mySize來設(shè)置子視圖的布局尺寸。同樣我們也可以通過設(shè)置子視圖的擴展屬性leftPos,rightPos,topPos,bottomPos,myLeft,myRight,myTop,myBottom來設(shè)置每個子視圖的外邊距值,對于流式布局來說外邊距值的設(shè)置具有特別的意義,就拿myLeft和myTop的設(shè)置為例子,子視圖所在的位置不同其所代表的意義是不同的。在一個垂直布局的情況下,如果子視圖是第一行一列則myLeft,myTop的值是這個子視圖離父布局視圖的邊距值;而當(dāng)子視圖是第二行一列時則myLeft是指定的離父布局視圖的左邊距值,而myTop則是離第一行整體子視圖的頂部邊距值;而當(dāng)子視圖是第一行二列是則myLeft是指定離前一個子視圖的左邊距值,而myTop則是離父布局視圖的頂部邊距值;而當(dāng)子視圖是二行二列時則myLeft和myTop則分別是前一個子視圖的左邊距值和第一行整體子視圖的頂部邊距值。下面的圖形可以很清楚的描述出這些設(shè)置的意義:


上面的圖表顯示了布局視圖的內(nèi)邊距padding設(shè)置,以及每個子視圖的外邊距設(shè)置值,以及可以很清楚的看到流式布局的每一行是如何確定出來的,以及當(dāng)另起一行時處于新行的子視圖的垂直位置是如何計算出來的。同時我們在圖中還看到了兩個間距:subviewHSpace和subviewVSpace的設(shè)置。這兩是布局的三個屬性:

@property(nonatomic ,assign)CGFloat subviewVSpace;

@property(nonatomic,assign) CGFloat subviewHSpace;

@property(nonatomic,assign) CGFloat subviewSpace;

其中的subviewSpace是上面兩個的整體設(shè)置值,這三個屬性的意義是設(shè)置所有視圖之間的行間距和列間距,其中subviewVSpace用于設(shè)置行間距,而subviewHSpace則是用于設(shè)置列間距,這兩個屬性的默認(rèn)值都是0。有時候我們不想為每個子視圖都設(shè)置四周的外邊距值,而希望所有的子視圖之間的行間距和列間距都是某個固定的值,這時候我們就可以通過直接設(shè)置這兩個屬性的值來進行所有子視圖之間的間距的設(shè)置,而不用分別為每個子視圖都去設(shè)置四周的邊距值。

對于數(shù)量約束流式布局來說,因為我們限制了一個方向上的子視圖的數(shù)量,有時候我們希望這個方向上的所有子視圖的尺寸都是一樣且平分這個方向上的布局視圖的尺寸,而不需要我們依次為所有子視圖指定尺寸,就像通過subviewVSpace和subviewHSpace設(shè)置視圖的行間距和列間距一樣,你可以設(shè)置gravity的屬性為MyGravity_Horz_Fill或者MyGravity_Vert_Fill這樣設(shè)置表示流式布局視圖里面的所有子視圖的尺寸都相等并且值是等于布局視圖的尺寸除以布局視圖指定的數(shù)量值或者拉伸所有子視圖的尺寸填滿整個布局視圖。

如果流式布局的方向是MyLayoutViewOrientation_Vert則表示每行的子視圖的寬度會被均分,這樣子視圖不需要指定寬度,但是布局視圖必須要指定一個明確的寬度值。

如果流式布局的方向是MyLayoutViewOrientation_Horz則表示每列的子視圖的高度會被均分,這樣子視圖不需要指定高度,但是布局視圖必須要指定一個明確的高度值,下面是屬性gravity設(shè)置為MyGravity_Horz_Fill(注意averageArrange已經(jīng)過期已經(jīng)被gravity替代)的兩種布局效果圖:


在一些場景中我們可以通過設(shè)置gravity為MyGravity_Fill,并且同時設(shè)置subviewVSpace和subviewHSpace的值來減少分別為每個子視圖設(shè)置位置和尺寸的工作量。另外在一些布局場景中我們還可以做如下的設(shè)置:

1.在垂直內(nèi)容填充約束布局中,我們可以設(shè)置某個子視圖的寬度和布局視圖的寬度建立約束關(guān)系,以及讓某個子視圖的高度同子視圖的寬度建立約束關(guān)系,也就是說可以設(shè)置子視圖.widthSize.equalTo(flowLayout.widthSize),以及子視圖.heightSize.equalTo(子視圖.widthSize)

2.在水平內(nèi)容填充約束布局中,我們可以設(shè)置某個子視圖的高度和布局視圖的高度建立約束關(guān)系,以及讓某個子視圖的寬度同子視圖的高度建立約束關(guān)系,也就是說可以設(shè)置子視圖.heightSize.equalTo(flowLayout.heightSize),以及子視圖.widthSize.equalTo(子視圖.heightSize)

3.在垂直數(shù)量約束布局中,我們可以設(shè)置某個子視圖的高度同子視圖的寬度建立約束關(guān)系,也就是說可以設(shè)置子視圖.heightSize.equalTo(子視圖.widthSize)

4.在水平數(shù)量約束布局中,我們可以設(shè)置某個子視圖的寬度同子視圖的高度建立約束關(guān)系,也就是說可以設(shè)置子視圖.widthSize.equalTo(子視圖.heightSize)

四、流式布局內(nèi)子視圖的停靠設(shè)置

在線性布局中我們可以讓所有的子視圖整體的停靠在布局視圖的一個特定的區(qū)域,這個可以通過線性布局的gravity屬性來設(shè)置。同樣在流式布局中我們也可以通過gravity屬性來設(shè)置流式布局中的所有子視圖都整體停靠在布局視圖的某個特定的區(qū)域。在有的時候我們的布局視圖設(shè)置有明確的高度和寬度值,同時我們又希望布局視圖里面的所有子視圖整體的停靠在布局視圖的某個區(qū)域,我們可以設(shè)置布局視圖的屬性:

@property(nonatomic,assign)MyGravity gravity;

屬性的默認(rèn)值是MyGravity_None表示不進行停靠處理,也就是默認(rèn)的從左上角開始進行子視圖的整體布局。

如果布局視圖方向為MyLayoutViewOrientation_Vert時可以為垂直布局視圖設(shè)置整體水平方向上的左,中,右整體停靠和寬度尺寸拉伸;而總是可以設(shè)置整體垂直方向上的上、中、下整體停靠。

如果布局視圖方向為MyLayoutViewOrientation_Horz時可以為水平布局視圖設(shè)置整體垂直方向上的上、中、下整體停靠和高度尺寸的拉伸;而總是可以設(shè)置整體水平方向上的左、中、右整體停靠。

gravity屬性是用來設(shè)置所有子視圖的整體停靠特性的,而在實際的應(yīng)用場景中我們還想進一步設(shè)置一行內(nèi)或者一列內(nèi)的視圖之間的停靠對齊方式。對于垂直布局來說,在一行內(nèi)的視圖之間的高度是可以不經(jīng)相同的。在一行之內(nèi)的視圖總是會存在有一個高度最高的子視圖,因此我們也希望這行內(nèi)的其他子視圖能以這個子視圖為基礎(chǔ)來進行垂直方向的對齊停靠設(shè)置(水平布局則是水平方向的對齊停靠設(shè)置)。因此我們可以通過屬性:

@property(nonatomic,assign)MyGravity arrangedGravity;

屬性來進行設(shè)置行內(nèi)或者列內(nèi)的視圖之間的對齊停靠方式,這個屬性默認(rèn)設(shè)置為MyGravity_None,表示不處理行內(nèi)停靠,也就是總是按左邊或者頂部對齊方式來布局行內(nèi)的子視圖。這個屬性的設(shè)置是依賴于布局的方向的。如果方向是MyLayoutViewOrientation_Vert則只用于表示每行子視圖的上中下停靠對齊位置,這個屬性只支持MyGravity_Vert_Top,MyGravity_Vert_Center,MyGravity_Vert_Bottom,MyGravity_Vert_Fill四種停靠對齊方式 這里的對齊基礎(chǔ)是以每行中的最高的子視圖為基準(zhǔn);如果方向是MyLayoutViewOrientation_Horz則只用于表示每列子視圖的左中右停靠對齊位置,這個屬性只支持MyGravity_Horz_Left,MyGravity_Horz_Center,MyGravity_Horz_Right,MyGravity_Horz_Fill四種停靠對齊方式 這里的對齊基礎(chǔ)是以每列中的最寬的子視圖為基準(zhǔn)。

這里需要注意的是arrangedGravity描述的所有的行內(nèi)或者列內(nèi)的停靠對齊方式,而不是只針對于某個一行或者一列,而gravity則用來描述所有子視圖整體的停靠位置。您可以通過流式布局庫的DEMO例子來調(diào)整具體的值來查看設(shè)置的結(jié)果。

五、流式布局和表格布局以及UICollectionView的區(qū)別以及應(yīng)用

在前面的文章中我們介紹了表格布局MyTalbeLayout,表格布局也可以用來建立多行多列布局的應(yīng)用場景。但在實際使用中還是有一些差別的。表格布局需要明確的指定建立一個新的行操作,同時又要明確的指定建立列的操作,同時表格布局的行和列的指定都是可以單獨指定的,而流失布局則沒有明確的行和列的概念,流失布局總是按一個方向進行排列,只要在遇到數(shù)量的約束和內(nèi)容的空間的約束時就是自動的進行換行處理,因此流時布局在建立子視圖時簡單而且方便。而針對UICollectionView來說也跟表格布局一樣需要明確的指定一共有多少行,每行有多少列,并且所有設(shè)置都是通過委托的形式來完成的,代碼量多而且操作起來麻煩。在實際的應(yīng)用中流式布局更加適合于用來建立那些標(biāo)簽流、九宮格菜單功能、枚舉功能等方面的布局。下面的圖片展示了流式布局的幾個DEMO效果:


六、總結(jié)

關(guān)于流式布局的功能就介紹到這了,流式布局是MyLayout布局系統(tǒng)里面的5大布局視圖之一,主要用于建立那些有規(guī)律排列和對齊的視圖的應(yīng)用場景,而且通過使用流式布局來建立界面布局使用的代碼量是最少而且最靈活的,視圖之間的排列順序的調(diào)整只需要調(diào)整其布局視圖中的順序就可以完成了。通過流式布局可以很簡單的來建立一套瀑布流風(fēng)格展示的界面,以及文字標(biāo)簽等功能。如果您想了解更多的關(guān)于流式布局的功能請您訪問我的github站點來了解更多:

https://github.com/youngsoft/MyLinearLayout


如果您覺得庫還不錯,記得為我點贊哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容