天天看點

【iOS】自定義collocationViewLayout實作瀑布流

其代理方法和屬性模仿UICollectionViewFlowLayout 所寫,使用方法和UICollectionViewFlowLayout類似

功能描述:

1 > 滿足UICollectionViewFlowLayout提供的普通的線性布局和網格布局

2 > 滿足單區和多區的瀑布流布局。

3 > 滿足多區瀑布流時每個區的列數可以不同

4 > 滿足設定header和footer

5 > 滿足設定header和footer的間距

注意:本文不涉及到裝飾視圖的相關代理方法以及計算。

首先要明白的事情:collectionView與collocationViewLayout的關系。

collocationView負責展示,collectionviewLayout負責提供如何展示,包括cell的大小位置,header和footer的大小位置等,UICollectionViewFlowLayout 繼承自UICollectionViewLayout是蘋果公司封裝好的layout,可以實作簡單的網格和線性布局,當cell的大小和間距一樣時可以用UICollectionViewFlowLayout,如果要實作比較複雜的布局,就需要自定義了。

其次,要了解UICollectionViewLayoutAttributes 類的屬性,以下是每一個cell的屬性,都是通過UICollectionViewLayoutAttributes屬性展現出來的。

1 2 3 4 5 6 7 8 9

CGRect frame; 

// cell的大小已經x,y值

CGPoint center;

//cell的中心點

CGSize size;

// cell的size

CATransform3D transform3D;

// cell的3D旋轉

CGRect bounds NS_AVAILABLE_IOS(7_0);

CGAffineTransform transform NS_AVAILABLE_IOS(7_0); 

// cell 的旋轉

CGFloat alpha;

//alp值

NSInteger zIndex; 

// default is 0 //z軸

getter=isHidden) BOOL hidden; 

// As an optimization,

還有,要了解UICollectionViewLayout的幾個方法:

1. prepareLayout :是專門用來準備布局的,在prepareLayout方法裡面我們可以事先就計算後面要用到的布局資訊并存儲起來,防止後面方法多次計算,提高性能。例如,我們可以在此方法就計算好每個cell的屬性、整個CollectionView的内容尺寸等等。此方法在布局之前會調用一次,之後隻有在調用invalidateLayout、shouldInvalidateLayoutForBoundsChange:傳回YES和UICollectionView重新整理的時候才會調用。

2. 

1

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

傳回對應的indexPath的cell的attributes

3. 

1

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

傳回對應的header和footer的attributes

4.

1

- (CGSize)collectionViewContentSize

;collectionView的size 這個size不是可視範圍的size是整個collectionView的size

5. 

1

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

 傳回在rect範圍内所有cell footer和head的attribute

了解以上的幾點就可以開始計算了。計算的順序是從上到下,即從區頭到每個區的cell再到區尾

設定一些數組用于存儲計算好的值:如下

1 2 3 4 5 6 7 8 9 10

//存放attribute的數組

@property (nonatomic, strong) NSMutableArray *attrsArray;

//存放目前區中各個列的目前的高度

@property (nonatomic, strong) NSMutableArray *columnHeights;

//collectionView的Content的高度

@property (nonatomic, assign) CGFloat contentHeight;

//記錄每個區最高的

@property (nonatomic, assign) CGFloat lastContentHeight;

//每個區的區頭和上個區的區尾的距離

@property (nonatomic, assign) CGFloat spacingWithLastSection;

首先是重寫 prepareLayout方法,也是最重要的一步。在此方法中完成初始化。所有的計算都置為零。

第一步:通過 

1

[self.collectionView numberOfSections]

方法擷取collectionView中一共有幾個區。設定一個for循環。

第二步:通過

1

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

擷取每個header的屬性。計算完成之後把attributes添加到attrsArray 數組中 ,同時還有根據sectionInsets 等參數改變contentHeight 的高度

第三步: 設定區頭完成之後,在循環中根據

1

[self.collectionView numberOfItemsInSection:i]

擷取相應的區有多少個cell

在這一步中計算是最麻煩的。可以分為如下步驟:

1> 計算每一個cell的frame。 根據此區中一共有幾列和螢幕的寬度,以及每個cell的之間的間距,計算出每個cell的寬度,因為高度是外面傳過來的,是以高度不需要計算 。那麼還需要知道cell的x值和y值。

x值:首先取出目前區的中哪一列最低。

1 2 3 4 5 6 7 8 9

NSInteger tempMinColumn = 

//預設第 0 列最小

CGFloat minColumnHeight = [self.columnHeights[

] doubleValue]; 

// 取出最小的那一列的高度

for

(NSInteger i = 

; i < self.columnCount; i ++) {

CGFloat columnH = [self.columnHeights[i] doubleValue];

if

(minColumnHeight > columnH) {

minColumnHeight = columnH;

tempMinColumn = i;

else

{}

}

tempMinColumn 就是最小的那一列

x值就可以根據sectionInsets , 每個cell的左右間距,和cell的寬度算出

1

CGFloat cellX = self.sectionInsets.left + tempMinColumn * (cellWeight + self.interitemSpacing);

y值:上面已經求出高度最小的那一列,以及最小的那一列的高度。

y值就 cellY = minColumnHeight

注意://如果cell的y值不等于上個區的最高的高度 即不是此區的第一列 要加上此區的每個cell的上下間距

1 2 3

if

(cellY != self.lastContentHeight) {

cellY += self.lineSpacing;

else

{}

這樣就可以知道了 cell的frame了, 即attributes.frame = CGRectMake(cellX, cellY, cellWeight, cellHeight);

2> 要更新 contentHeight (目前collectionView的内容的高度) 和columnHeights(當區的每列的高度或者說每列的最後一個cell的y值 + height)

那麼這樣相應cell的值就計算完畢 ,在此函數傳回值處添加到attrsArray 中去。

第四部:同header的計算方式一樣 在

1

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

計算footer的frame

一共多少個區 ,每個區的header的frame是多少,每個區中有多少個cell 每個cell的frame是多少 ,每個區的footer的frame是多少,以此循環計算出所有的attributes,在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 傳回計算的attributes

  • 注意 :在計算每個attributes時 collectionView的内容的高度即contentHeight collectionView上個區的最高的那一列的高度即lastContentHeight 都在改變。

1 2 3

- (CGSize)collectionViewContentSize {

return

CGSizeMake(self.collectionView.frame.size.width, self.contentHeight);

}

中傳回collectionView ContentSize 完成布局。

為了支援擴充性和易用性,我完全模仿 UICollectionViewFlowLayout 的用法設定代理方法和屬性。至于其使用方法,和UICollectionViewFlowLayout 一樣的。代理方法和屬性如下。

1 2 3 4 5 6 7 8 9 10 11 12 13

@property (nonatomic, weak) id delegate;

// 區的sectionInsets

@property (nonatomic,assign) UIEdgeInsets sectionInsets;

//每個區的列數

@property (nonatomic,assign) NSInteger columnCount;

// 每個cell的上下間距

@property (nonatomic,assign) CGFloat lineSpacing;

//每個cell的左右間距

@property (nonatomic,assign) CGFloat interitemSpacing;

//header的size

@property (nonatomic,assign) CGSize headerReferenceSize;

// footer的size

@property (nonatomic,assign) CGSize footerReferenceSize;

上述的這些參數 如果每個區都一樣,則可以在layout初始化的時候設定,如過每個區的參數設定都不一樣,比如第一個區是兩列,第二個區是一列,不用擔心,用代理。

代理方法支援分區設定這些參數。

@protocol JWCCustomLayoutDelegate

@required

// cell 高

1

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout heightForRowAtIndexPath:(NSIndexPath *)indexPath itemWidth:(CGFloat)itemWidth ;

@optional

// headersize

1

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;

// footer 的 size

1

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;

// 每個區的邊距

1

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

// 每個區多少列

1

- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout columnNumberAtSection:(NSInteger )section;

// 每個區多少中行距

1

- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout lineSpacingForSectionAtIndex:(NSInteger)section;

// 每個 item 之間的左右間距

1

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout interitemSpacingForSectionAtIndex:(NSInteger)section;

// 本區區頭和上個區區尾的間距

1

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout spacingWithLastSectionForSectionAtIndex:(NSInteger)section; (注意:在collectionViewFolwLayout中是無法設定目前的區頭和上個區尾的間距的,為了彌補這一缺憾,特此添加這個方法)

【iOS】自定義collocationViewLayout實作瀑布流
【iOS】自定義collocationViewLayout實作瀑布流
【iOS】自定義collocationViewLayout實作瀑布流