1. 程式人生 > >瀑布流(自定義佈局實現)

瀑布流(自定義佈局實現)

這篇文章主要分享如何用自定義佈局來實現瀑布流,關於瀑布流的其他實現方式可以參考我的另一篇文章 瀑布流(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實現要簡單很多呢!