上篇博客的實例是自帶的UICollectionViewDelegateFlowLayout佈局基礎上來作的Demo, 詳情請看《iOS開發之窺探UICollectionViewController(二) --詳解CollectionView各類回調》。UICollectionView之因此強大,是由於其具備自定義功能,這一自定義就不得了啦,自由度很是大,定製的高,因此功能也是灰常強大的。本篇博客就不使用自帶的流式佈局了,咱們要自定義一個瀑布流。自定義的瀑布流能夠配置其參數: 每一個Cell的邊距,共有多少列,Cell的最大以及最小高度是多少等。html
一.先入爲主git
先來看一下不一樣配置參數下運行後的效果吧,每張截圖的列數和Cell之間的邊距都有所不一樣,瀑布流的列數依次爲2,3,8。有密集恐懼證的童鞋就不要看這些運行效果圖了,真的會看暈的。下面這些運行效果就是修改不一樣的配置參數來進行佈局的。看圖吧,關於瀑布流的效果就不囉嗦了。如下的效果就是使用自定義佈局作的,接下來將會介紹一下其實現原理。github
二. UICollectionViewLayout數組
在介紹上述效果實現原理以前,須要介紹一下UICollectionViewLayout。UICollectionView的自定義功能就是本身去實現UICollectionViewLayout的子類,而後重寫相應的方法來實現Cell的佈局,先介紹一下須要重寫的方法,而後再此方法上進行應用實現上述瀑布流。好,廢話少說,幹活走起。dom
1.佈局預加載函數ide
當佈局首次被加載時會調用prepareLayout函數,見名知意,就是預先加載佈局,在該方法中能夠去初始化佈局相關的數據。該方法相似於視圖控制器的ViewDidLoad方法,稍後回用到該方法。
函數
1 // The collection view calls -prepareLayout once at its first layout as the first message to the layout instance. 2 // The collection view calls -prepareLayout again after layout is invalidated and before requerying the layout information. 3 // Subclasses should always call super if they override. 4 - (void)prepareLayout;
2.內容滾動範圍佈局
下方是定義ContentSize的方法。該方法會返回CollectionView的大小,這個方法也是自定義佈局中必須實現的方法。說白了,就是設置ScrollView的ContentSize,即滾動區域。post
1 // Subclasses must override this method and use it to return the width and height of the collection view’s content. These values represent the width and height of all the content, not just the content that is currently visible. The collection view uses this information to configure its own content size to facilitate scrolling. 2 - (CGSize)collectionViewContentSize;
3.肯定佈局屬性優化
下方四個方法是肯定佈局屬性的,下方第一個方法返回一個數組,該數組中存放的是爲每一個Cell綁定的UICollectionViewLayoutAttributes屬性,便於在下面第二個方法中去定製每一個Cell的屬性。第三個方法就是根據indexPath來獲取Cell所綁定的layoutAtrributes, 而後去更改UICollectionViewLayoutAttributes對象的一些屬性並返回,第四個是爲Header View或者FooterView來定製其對應的UICollectionViewLayoutAttributes,而後返回。
1 // UICollectionView calls these four methods to determine the layout information. 2 // Implement -layoutAttributesForElementsInRect: to return layout attributes for for supplementary or decoration views, or to perform layout in an as-needed-on-screen fashion. 3 // Additionally, all layout subclasses should implement -layoutAttributesForItemAtIndexPath: to return layout attributes instances on demand for specific index paths. 4 // If the layout supports any supplementary or decoration view types, it should also implement the respective atIndexPath: methods for those types. 5 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect 6 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; 7 - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath; 8 - (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
4.UICollectionViewLayoutAttributes
下方是UICollectionViewLayoutAttributes經常使用的屬性,你能夠在上面第二個方法中去爲下方這些屬性賦值,爲Cell定製屬於本身的Attributes。由下方的屬性就對自定義佈局的的強大,在本篇博客中只用到了下方的一個屬性,那就是frame。
1 @property (nonatomic) CGRect frame; 2 @property (nonatomic) CGPoint center; 3 @property (nonatomic) CGSize size; 4 @property (nonatomic) CATransform3D transform3D; 5 @property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0); 6 @property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0); 7 @property (nonatomic) CGFloat alpha; 8 @property (nonatomic) NSInteger zIndex; // default is 0 9 @property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
三. UICollectionViewLayout的應用
通過上面的簡單介紹,想必對UICollectionViewLayout有必定的瞭解吧,UICollectionViewLayout中還有好多方法,之後用到的時候在給你們介紹。接下來要使用自定義佈局來實現瀑布流。咱們須要在UICollectionViewLayout的子類中實現相應的佈局方法,由於UICollectionViewLayout是虛基類,是不能直接被實例化的,因此咱們須要新建一個佈局類,這個佈局類繼承自UICollectionViewLayout。而後去實現上述方法,給每一個Cell定製不一樣的UICollectionViewLayoutAttributes。好了仍是拿代碼說話吧。
1.重寫prepareLayout方法去初始化一些數據,該方法在CollectionView從新加載時只會調用一次,因此把一些參數的配置,計算每一個Cell的寬度,每一個Cell的高度等代碼放在預處理函數中。在該函數中具體調用的函數以下所示:
1 #pragma mark -- <UICollectionViewLayout>虛基類中重寫的方法 2 3 /** 4 * 該方法是預加載layout, 只會被執行一次 5 */ 6 - (void)prepareLayout{ 7 [super prepareLayout]; 8 9 [self initData]; 10 11 [self initCellWidth]; 12 13 [self initCellHeight]; 14 15 }
2.返回內容的範圍,即爲CollectionView設定ContentSize。ContentSize的Width就是屏幕的寬度,而ContentSize的高度是一列中最後一個Cell的Y座標加上其自身高度的最大值。在此函數中會調用求CellY數組中的最大值。具體實現代碼以下:
1 /** 2 * 該方法返回CollectionView的ContentSize的大小 3 */ 4 - (CGSize)collectionViewContentSize{ 5 6 CGFloat height = [self maxCellYArrayWithArray:_cellYArray]; 7 8 return CGSizeMake(SCREEN_WIDTH, height); 9 }
3.下面的方法是爲每一個Cell去綁定一個UICollectionViewLayoutAttributes對象,而且以數組的形式返回,在咱們的自定義瀑布流中,咱們只自定義了Cell的frame,就能夠實現咱們的瀑布流,UICollectionViewLayoutAttributes的其餘屬性咱們沒有用到,由此能夠看出自定義Cell佈局功能的強大。
1 /** 2 * 該方法爲每一個Cell綁定一個Layout屬性~ 3 */ 4 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 5 { 6 7 [self initCellYArray]; 8 9 NSMutableArray *array = [NSMutableArray array]; 10 11 //add cells 12 for (int i=0; i < _numberOfCellsInSections; i++) 13 { 14 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; 15 16 UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; 17 18 [array addObject:attributes]; 19 } 20 21 return array; 22 23 }
4. 經過下述方法設定每一個Cell的UICollectionViewLayoutAttributes對象的參數,爲了實現瀑布流因此咱們只須要設置每一個Cell的frame便可。每一個cell的frame的肯定是以列來定的,有所在列的上個Cell的Y座標來肯定下個cell的位置。瀑布流實現關鍵點以下:
(1)Cell寬度計算:若是瀑布流的列數和Cell的Padding肯定了,那麼每一個Cell的寬度再經過屏幕的寬度就能夠計算出來了。
(2)Cell高度計算:經過隨機數生成的高度
(3)Cell的X軸座標計算:經過列數,和Padding,以及每一個Cell的寬度很容易就能夠計算出每一個Cell的X座標。
(4)Cell的Y軸座標計算:經過Cell所在列的上一個Cell的Y軸座標,Padding, 和 上一個Cell的高度就能夠計算下一個Cell的Y座標,並記錄在Y座標的數組中了。
/** * 該方法爲每一個Cell綁定一個Layout屬性~ */ - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; CGRect frame = CGRectZero; CGFloat cellHeight = [_cellHeightArray[indexPath.row] floatValue]; NSInteger minYIndex = [self minCellYArrayWithArray:_cellYArray]; CGFloat tempX = [_cellXArray[minYIndex] floatValue]; CGFloat tempY = [_cellYArray[minYIndex] floatValue]; frame = CGRectMake(tempX, tempY, _cellWidth, cellHeight); //更新相應的Y座標 _cellYArray[minYIndex] = @(tempY + cellHeight + _padding); //計算每一個Cell的位置 attributes.frame = frame; return attributes; }
5. initData方法主要是對數據進行初始化,在本篇博客中爲了先實現效果,咱們暫且把數據給寫死。下篇博客會在本篇博客中的基礎上進行優化和改進,這些配置參數都會在Delegate中提供,便於靈活的去定製屬於你本身的瀑布流。本篇博客中Demo的配置項先寫死就OK了,仍是那句話,下篇博客中會給出一些相應的代理,來定製咱們的瀑布流。
/** * 初始化相關數據 */ - (void) initData{ _numberOfSections = [self.collectionView numberOfSections]; _numberOfCellsInSections = [self.collectionView numberOfItemsInSection:0]; //經過回調獲取列數 _columnCount = 5; _padding = 5; _cellMinHeight = 50; _cellMaxHeight = 150; }
6.下方的方法是根據Cell的列數來求出Cell的寬度。由於Cell的寬度都是同樣的,每一個Cell的間隔也是必定的。例若有5列Cell, 那麼Cell中間的間隔就有4(5-1)個,那麼每一個Cell的寬度就是屏幕的寬度減去全部間隔的寬度,再除以列數就是Cell的寬度。若是沒聽我囉嗦明白的話,直接看代碼吧,並不複雜。每一個Cell的寬度和間隔肯定了,那麼每一個Cell的X軸座標也就肯定了。代碼以下:
1 /** 2 * 根據Cell的列數求出Cell的寬度 3 */ 4 - (void) initCellWidth{ 5 //計算每一個Cell的寬度 6 _cellWidth = (SCREEN_WIDTH - (_columnCount -1) * _padding) / _columnCount; 7 8 //爲每一個Cell計算X座標 9 _cellXArray = [[NSMutableArray alloc] initWithCapacity:_columnCount]; 10 for (int i = 0; i < _columnCount; i ++) { 11 12 CGFloat tempX = i * (_cellWidth + _padding); 13 14 [_cellXArray addObject:@(tempX)]; 15 } 16 17 }
6. 根據Cell的最小高度和最大高度來利用隨機數計算每一個Cell的高度,把每一個Cell的高度記錄在數組中,便於Cell加載時使用。具體代碼以下:
1 /** 2 * 隨機生成Cell的高度 3 */ 4 - (void) initCellHeight{ 5 //隨機生成Cell的高度 6 _cellHeightArray = [[NSMutableArray alloc] initWithCapacity:_numberOfCellsInSections]; 7 for (int i = 0; i < _numberOfCellsInSections; i ++) { 8 9 CGFloat cellHeight = arc4random() % (_cellMaxHeight - _cellMinHeight) + _cellMinHeight; 10 11 [_cellHeightArray addObject:@(cellHeight)]; 12 } 13 14 }
7.初始化Cell的Y軸座標數組,由於是瀑布流,瀑布流的特色是每列中Cell的X軸座標是相同的,咱們只須要根據本列上一個Cell的Y軸座標來肯定本列中將要插入Cell的Y軸座標,全部咱們須要維護一個每列當前Cell的Y軸座標數組。其初始化方法以下:
/** * 初始化每列Cell的Y軸座標 */ - (void) initCellYArray{ _cellYArray = [[NSMutableArray alloc] initWithCapacity:_columnCount]; for (int i = 0; i < _columnCount; i ++) { [_cellYArray addObject:@(0)]; } }
8.下面的方法是求Cell的Y軸座標數組的最大值,由於在Cell都加載完畢後,Cell數組中最大值就是CollectionView的ContentSize的Height的值。
1 /** 2 * 求CellY數組中的最大值並返回 3 */ 4 - (CGFloat) maxCellYArrayWithArray: (NSMutableArray *) array{ 5 if (array.count == 0) { 6 return 0.0f; 7 } 8 9 CGFloat max = [array[0] floatValue]; 10 for (NSNumber *number in array) { 11 12 CGFloat temp = [number floatValue]; 13 14 if (max < temp) { 15 max = temp; 16 } 17 } 18 19 return max; 20 }
9.下方代碼是求CellY數組中的第一個最小值的索引,由於求出這個CellY數組中的第一個Cell最新值得索引就是Cell應該插入的列。
1 /** 2 * 求CellY數組中的最小值的索引 3 */ 4 - (CGFloat) minCellYArrayWithArray: (NSMutableArray *) array{ 5 6 if (array.count == 0) { 7 return 0.0f; 8 } 9 10 NSInteger minIndex = 0; 11 CGFloat min = [array[0] floatValue]; 12 13 for (int i = 0; i < array.count; i ++) { 14 CGFloat temp = [array[i] floatValue]; 15 16 if (min > temp) { 17 min = temp; 18 minIndex = i; 19 } 20 } 21 22 return minIndex; 23 }
自定義集合視圖控制器佈局第一階段就先到這,下篇博客會在此基礎上進一步開發。把上述寫死的配置參數,經過Delegate提供,使其在UICollectionView可進行配置,其配置方式相似於UICollectionViewDelegateFlowLayout的代理方法。
上述代碼gitHub分享地址:https://github.com/lizelu/CustomCollectionViewLayout
做者:青玉伏案
出處:http://www.cnblogs.com/ludashi/ 本文版權歸做者和共博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。 若是文中有什麼錯誤,歡迎指出。以避免更多的人被誤導。