思路
- 瀑布流核心思想是,每張圖片都是從高度最低的一列去拼接,同時圖片的尺寸取決于需求,而不是統(tǒng)一的尺寸,進而形成參差不齊的效果
- 通過UICollectionView的自定義UICollectionViewLayout(布局)去實現(xiàn)
- 相比用ScrollerView去實現(xiàn)瀑布流,UICollectionView更為簡單,因為它已經(jīng)具備循環(huán)利用的功能,如果使用ScrollerView還需要自己去實現(xiàn)循環(huán)利用
步驟
- 新建一個UICollectionViewWaterLayout,繼承UICollectionViewLayout
- 設(shè)置代理UICollectionViewWaterLayoutDelegate,用于獲取每個item的高度、列數(shù)、列間距、行間距等,其中item的高度是必須的,外界通過內(nèi)部提供的寬度,按比例計算出高度。之所以用代理的方式去獲取這些屬性,而不是直接用property屬性,是因為代理什么時候調(diào)用是內(nèi)部決定的,當用property屬性去獲取這些屬性時,外界可以在任何時候去改變這些屬性,這就意味著內(nèi)部要隨時針對這種改變?nèi)プ鲰憫?yīng)處理(重寫set方法)比較麻煩。
protocol UICollectionViewWaterLayoutDelegate <NSObject>
@required
// 返回item的高度(根據(jù)width按比例去計算)
- (CGFloat)waterLayout:(UICollectionViewWaterLayout *)waterLayout heightForItemAtIndexPath:(NSIndexPath *)indexPath andWidth:(CGFloat)width;
@optional
// 返回列數(shù)
- (NSInteger)numberOfColumnsInWaterLayout:(UICollectionViewWaterLayout *)waterLayout;
// 返回行間距
- (CGFloat)rowMarginForWaterLayout:(UICollectionViewWaterLayout *)waterLayout;
// 返回列間距
- (CGFloat)columnMarginForWaterLayout:(UICollectionViewWaterLayout *)waterLayout;
// 返回內(nèi)邊距
- (UIEdgeInsets)edgeInsetsForWaterLayout:(UICollectionViewWaterLayout *)waterLayout;
@end
/** 緩存每個item的UICollectionViewLayoutAttributes屬性,避免重復(fù)計算 */
property (nonatomic, strong) NSMutableArray *attributesArr;
/** 記錄每列的高度 */
property (nonatomic, strong) NSMutableArray *columnHeights;
- 重寫prepareLayout方法去初始化布局,注意要先調(diào)用父類同名方法
- (void)prepareLayout {
// 要先調(diào)用父類的prepareLayout
[super prepareLayout];
// 清空原來緩存的列的高度和cell的未知屬性,否則當重新調(diào)用prepareLayout,會累加
[self.attributesArr removeAllObjects];
[self.columnHeights removeAllObjects];
// 設(shè)置列高度初始值為頂部內(nèi)邊距
for (int i = 0; i < layoutColumn; i++) {
[self.columnHeights addObject:[NSNumber numberWithDouble:layoutInsets.top]];
}
// 設(shè)置每個item的布局屬性,并緩存
NSInteger itemsTotal = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < itemsTotal; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = [self getItemFrameWithIndexPath:indexPath]; [self.attributesArr addObject:attributes];
}
}
- 瀑布流核心計算方法,之所以自己定義一個方法去計算,而不是直接在layoutAttributesForItemAtIndexPath:實現(xiàn)計算方法,并調(diào)用它來獲取每個item的位置屬性,是因為layoutAttributesForItemAtIndexPath:會被系統(tǒng)重復(fù)調(diào)用,導(dǎo)致列的高度計算錯誤
/** 每個item都是放到當前高度最低的列下面
* 要計算x,y,就要找到高度最低的列
* 寬度 = (collectionView寬度 - 左內(nèi)邊距 - 右內(nèi)邊距 - (列數(shù) - 1)*列間距)/列數(shù) * 高度通過代理獲取
* x = 左內(nèi)邊距 + 列號 * (寬度 + 列間距)
* y = 列的高度 + 行間距
*/
- (CGRect)getItemFrameWithIndexPath:(NSIndexPath *)indexPath {
// 高度最低的列
CGFloat minHeight = [self.columnHeights[0] doubleValue];
NSInteger minHeightCol = 0;
for (int i = 1; i < self.columnHeights.count; i++) {
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (minHeight > columnHeight) {
minHeight = columnHeight; minHeightCol = i;
}
}
// 計算item的位置
CGFloat collectionViewW = self.collectionView.frame.size.width;
CGFloat itemW = (collectionViewW - self.insets.left - self.insets.right - (self.columns - 1)*self.columnMargin) / self.columns;
// 通過使用者實現(xiàn)的代理拿到item的高度
CGFloat itemH = [self.delegate waterLayout:self heightForItemAtIndexPath:indexPath andWidth:itemW];
CGFloat itemX = (itemW + self.columnMargin) * minHeightCol;
CGFloat itemY = minHeight + self.rowMargin; CGRect frame = CGRectMake(itemX, itemY, itemW, itemH);
// 更新列的高度
self.columnHeights[minHeightCol] = [NSNumber numberWithDouble:CGRectGetMaxY(frame)];
return frame;
}
- 重寫layoutAttributesForElementsInRect:,告訴collectionView每個item以什么形式排布,這個方法會被反復(fù)調(diào)用,所以不適合將計算過程寫在這個里面,而是直接用上面計算好的緩存數(shù)據(jù)
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return self.attributesArr;
}
- 重寫layoutAttributesForItemAtIndexPath:方法,如果不重寫,UICollectionView在調(diào)用切換布局方式的方法時會崩潰
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.attributesArr[indexPath.item];
}
- 重寫collectionViewContentSize,否則UICollectionView無法滾動,返回的就是滾動范圍
- (CGSize)collectionViewContentSize {
// 找到高度最高的列
// 滾動高度 = 高度最高的列的高度 + 底部內(nèi)邊距
// 記錄最低高度
CGFloat maxHeight = [self.columnHeights[0] doubleValue];
for(int i = 1; i < self.columnHeights.count; i++) {
CGFloat columnHeight = [self.columnHeights[i] doubleValue];
if (maxHeight < columnHeight) {
maxHeight = columnHeight;
}
}
return CGSizeMake(0, maxHeight + layoutInsets.bottom);
}
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。