瀑布流(自定義佈局實現)
這篇文章主要分享如何用自定義佈局來實現瀑布流,關於瀑布流的其他實現方式可以參考我的另一篇文章 瀑布流(UIScrollView實現),利用UICollectionView實現瀑布流有個非常大的好處就是我們不用關心重用機制,只把注重點放在如何自定義佈局來排布每一個cell的位置
新建一個佈局DSWaterFlowLayout,繼承自UICollectionViewLayout
一、提供可用介面(列數,行間距,列間距,邊距)
在.h檔案中:
/**
* 每一行的間距
*/
@property (nonatomic, assign) CGFloat rowMargin;
/**
* 每一列的間距
*/
@property (nonatomic, assign) CGFloat columnMargin;
/**
* 周圍的edgeInset
*/
@property (nonatomic, assign) UIEdgeInsets sectionEdgeInset;
/**
* 列數
*/
@property (nonatomic, assign) NSInteger columnsCount;
在.m檔案中的init方法初始化預設值:
- (instancetype)init
{
if (self = [super init]) {
self.rowMargin = 10 ;
self.columnMargin = 10;
self.columnsCount = 3;
self.sectionEdgeInset = UIEdgeInsetsMake(10, 10, 10, 10);
}
return self;
}
二、 重寫layoutAttributesForItemAtIndexPath方法和shouldInvalidateLayoutForBoundsChange方法,這個方法的目的就是排布每一個cell的位置,然後把cell的UICollectionViewLayoutAttributes屬性返回出去
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
1> 實現思路就是:判斷每一列的最大Y值,找出Y值最小的一列,然後新增cell到這一列,所以我們可以建立一個字典來裝每一列的最大Y值,key就是列數,value就是最大Y值
/**
* 用來裝載最大Y值的字典
*/
@property (nonatomic, strong) NSMutableDictionary *maxYDict;
- (NSMutableDictionary *)maxYDict
{
if (_maxYDict== nil) {
self.maxYDict = [[NSMutableDictionary alloc] init];
for (int i = 0; i < self.columnsCount; i++) {
NSString *column = [NSString stringWithFormat:@"%d", i];
self.maxYDict[column] = @(self.sectionEdgeInset.top);
}
}
return _maxYDict;
}
2> 遍歷字典,找出Y值最小的一列,然後設定width,X,Y值,同時記得跟新Y值,至於height則需要圖片真實的寬高比,所以我們需要拿到模型,但是為了降低耦合性和可用性,因此可以用代理
在.h檔案中:
@protocol DSWaterFlowLayoutDelegate <NSObject>
@required
/**
* 根據圖片實際的寬高比和寬度,算出實際的高度
*/
- (CGFloat)waterFlowLayout:(DSWaterFlowLayout *)waterFlowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath;
@end
@property (nonatomic,weak) id<DSWaterFlowLayoutDelegate> delegate;
在.m檔案中:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"0-----");
// 預設最小的一列是第0列
__block NSString *minColumn = @"0";
// 遍歷字典,找出Y值為最小的那列
[self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString * column, NSNumber *maxY, BOOL * _Nonnull stop) {
if ([self.maxYDict[column] integerValue] < [self.maxYDict[minColumn] integerValue]) {
minColumn = column;
}
}];
// 計算尺寸
CGFloat width = (self.collectionView.frame.size.width - self.sectionEdgeInset.left - self.sectionEdgeInset.right - (self.columnsCount - 1) * self.columnMargin) / self.columnsCount;
CGFloat height = [self.delegate waterFlowLayout:self heightForWidth:width atIndexPath:indexPath];
// 計算位置
CGFloat x = self.sectionEdgeInset.right + (width + self.columnMargin) * [minColumn integerValue];
CGFloat y = [self.maxYDict[minColumn] integerValue] + self.rowMargin;
// 更新Y值
self.maxYDict[minColumn] = @(y + height);
// 取得indexPath位置上cell的UICollectionViewLayoutAttributes
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.frame = CGRectMake(x, y, width, height);
return attrs;
}
三、告訴collectionView的滾動範圍
- (CGSize)collectionViewContentSize
{
// 預設最小的一列是第0列
__block NSString *maxColumn = @"0";
// 遍歷字典,找出Y值為最小的那列
[self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString * column, NSNumber *maxY, BOOL * _Nonnull stop) {
if ([self.maxYDict[column] integerValue] > [self.maxYDict[maxColumn] integerValue]) {
maxColumn = column;
}
}];
return CGSizeMake(0, [self.maxYDict[maxColumn] integerValue] + self.sectionEdgeInset.bottom);
}
其實這個時候佈局就完成了,但是程式還是有些問題。首先,在滾動的時候collectionView裡面的所有cell會重新算,因此字典中的最大Y值會越來越大,所以在重新佈局的時候應該清空字典。
在滾動的時候,系統會呼叫一次prepareLayout方法,兩次layoutAttributesForElementsInRect方法,所以我們應該在prepareLayout方法中清空字典,並且儲存所有算好的UICollectionViewLayoutAttributes屬性,因此建立一個數組來儲存UICollectionViewLayoutAttributes屬性,並且只在prepareLayout方法中算,只需要在layoutAttributesForElementsInRect方法中返回算好的屬性就行。
/**
* 存放所有的佈局屬性
*/
@property (nonatomic, strong) NSMutableArray *attrsArray;
- (NSMutableArray *)attrsArray
{
if (_attrsArray== nil) {
self.attrsArray = [[NSMutableArray alloc] init];
}
return _attrsArray;
}
- (void)prepareLayout
{
[super prepareLayout];
// 清空Y值
for (int i = 0; i < self.columnsCount; i++) {
NSString *column = [NSString stringWithFormat:@"%d", i];
self.maxYDict[column] = @(self.sectionEdgeInset.top);
}
NSInteger count = [self.collectionView numberOfItemsInSection:0];
// 清空之前的屬性
[self.attrsArray removeAllObjects];
for (int i = 0; i < count; i++) {
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
[self.attrsArray addObject:attrs];
}
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
至此,整個佈局就完成了,是不是感覺比UIScrollView實現要簡單很多呢!