實現瀑布流簡單,實現分割槽瀑布流,並且每個區的瀑布流的列數不一樣且有區頭和區尾,就不是太容易了。我嫌麻煩不願意自己寫(——>我承認懶,不願意動腦子 V)開始在網上找了好多,都是僅僅一個區的瀑布流,沒區頭和區尾,完全滿足不了我的需求。沒辦法,產品的需求在那,不能不做吧,於是自己靜下心來開始寫。
其代理方法和屬性模仿UICollectionViewFlowLayout 所寫,使用方法和UICollectionViewFlowLayout類似
功能描述:
-
滿足UICollectionViewFlowLayout提供的普通的線性佈局和網格佈局
-
滿足單區和多區的瀑布流佈局。
-
滿足多區瀑布流時每個區的列數可以不同
-
滿足設定header和footer
-
滿足設定header和footer的間距
注意:本文不涉及到裝飾檢視的相關代理方法以及計算。
首先要明白的事情:collectionView與collocationViewLayout的關係。
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的幾個方法:
-
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
在這一步中計算是最麻煩的。可以分為如下步驟:
- 計算每一個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);
複製程式碼
- 要更新 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 謝謝
看完如果對你有用 請點贊鼓勵下中不中?? ?
效果圖如下