自定義collocationViewLayout實現多區瀑布流

 實現瀑布流簡單,實現分區瀑布流,而且每一個區的瀑布流的列數不同且有區頭和區尾,就不是太容易了。我嫌麻煩不肯意本身寫(——>我認可懶,不肯意動腦子 V)開始在網上找了好多,都是僅僅一個區的瀑布流,沒區頭和區尾,徹底知足不了個人需求。沒辦法,產品的需求在那,不能不作吧,因而本身靜下心來開始寫。git

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

功能描述:數組

  1. 知足UICollectionViewFlowLayout提供的普通的線性佈局和網格佈局bash

  2. 知足單區和多區的瀑布流佈局。函數

  3. 知足多區瀑布流時每一個區的列數能夠不一樣佈局

  4. 知足設置header和footerpost

  5. 知足設置header和footer的間距性能

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

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

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

其次,要了解UICollectionViewLayoutAttributes 類的屬性,如下是每個cell的屬性,都是經過UICollectionViewLayoutAttributes屬性體現出來的。

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刷新的時候纔會調用。

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
複製代碼

返回對應的indexPath的cell的attributes

-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
複製代碼

返回對應的header和footer的attributes

- (CGSize)collectionViewContentSize
複製代碼

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

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 
複製代碼

返回在rect範圍內全部cell footer和head的attribute

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

設置一些數組用於存儲計算好的值:以下

//存放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方法,也是最重要的一步。在此方法中完成初始化。全部的計算都置爲零。

第一步:經過

[self.collectionView numberOfSections]
複製代碼

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

第二步:經過

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
複製代碼

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

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

[self.collectionView numberOfItemsInSection:i]
複製代碼

獲取相應的區有多少個cell

在這一步中計算是最麻煩的。能夠分爲以下步驟:

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

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

NSInteger tempMinColumn = 0; //默認第 0 列最小

CGFloat minColumnHeight = [self.columnHeights[0] doubleValue]; //
複製代碼

取出最小的那一列的高度

for (NSInteger i = 0; i < self.columnCount; i ++) {

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

if (minColumnHeight > columnH) {

minColumnHeight = columnH;

tempMinColumn = i;

} else {}

}
複製代碼

tempMinColumn 就是最小的那一列

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

CGFloat cellX = self.sectionInsets.left + tempMinColumn * (cellWeight + self.interitemSpacing);
複製代碼

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

y值就 cellY = minColumnHeight

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

if (cellY != self.lastContentHeight) {

cellY += self.lineSpacing;

} else {}
複製代碼

這樣就能夠知道了 cell的frame了, 即

attributes.frame = CGRectMake(cellX, cellY, cellWeight, cellHeight);
複製代碼
  1. 要更新 contentHeight (當前collectionView的內容的高度) 和columnHeights(當區的每列的高度或者說每列的最後一個cell的y值 + height)

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

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

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
複製代碼

計算footer的frame

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

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

- (CGSize)collectionViewContentSize {

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

}
複製代碼

中返回collectionView ContentSize 完成佈局。

爲了支持擴展性和易用性,我徹底模仿 UICollectionViewFlowLayout 的用法設置代理方法和屬性。至於其使用方法,和UICollectionViewFlowLayout 同樣的。代理方法和屬性以下。

@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 高

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout heightForRowAtIndexPath:(NSIndexPath *)indexPath itemWidth:(CGFloat)itemWidth ;
複製代碼
@optional

// headersize

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
複製代碼

// footer 的 size

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
複製代碼

// 每一個區的邊距

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
複製代碼

// 每一個區多少列

- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout columnNumberAtSection:(NSInteger )section;
複製代碼

// 每一個區多少中行距

- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout lineSpacingForSectionAtIndex:(NSInteger)section;
複製代碼

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

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout interitemSpacingForSectionAtIndex:(NSInteger)section;
複製代碼

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

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout spacingWithLastSectionForSectionAtIndex:(NSInteger)section;  (注意:在collectionViewFolwLayout中是沒法設置當前的區頭和上個區尾的間距的,爲了彌補這一缺憾,特此添加這個方法)
複製代碼

以上只是大體計算步驟,具體實現代碼見Demo在這

轉載請註明出處https://juejin.im/post/5a5274e46fb9a01c9f5b3eee 謝謝

看完若是對你有用 請點贊鼓勵下中不中?🤣 🤣

效果圖以下

第0區三列第一區四列

區頭和區尾設置間距

單個區兩列效果圖
相關文章
相關標籤/搜索