瀑布流(自定义布局实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ldszw/article/details/50757664

这篇文章主要分享如何用自定义布局来实现瀑布流,关于瀑布流的其他实现方式可以参考我的另一篇文章 瀑布流(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实现要简单很多呢!

猜你喜欢

转载自blog.csdn.net/ldszw/article/details/50757664