简单瀑布流的实现

关于简单瀑布流的实现:


瀑布流是基于UICollectionView来实现的,主要通过自定义的一个继承于UICollectionViewFlowLayout的类


首先,实现基本的界面实现.

然后,在自定义的一个继承于UICollectionViewFlowLayout的类QHLFlowLayout中.
瀑布流的实现,主要是重写了4个方法来实现
在继承于UICollectionViewFlowLayout的QHLFlowLayout.h文件中

@protocol QHLFlowLayoutDelegate <NSObject>

- (CGFloat)flowLayout:(QHLFlowLayout *)flowLayout heightForItems:(NSIndexPath *)indexPath itemWithWidth:(CGFloat)width;

- (NSInteger)flowLayoutNumberOfItemColumnCount:(QHLFlowLayout *)flowLayout;

- (CGFloat)flowLayoutMarginBetweenItems:(QHLFlowLayout *)flowLayout;

- (UIEdgeInsets)flowLayoutSectionInsetOfItems:(QHLFlowLayout *)flowLayout;

@end

以上是自定义的flowLayout的代理方法.

通过代理,来实现对每个item的高度,item的间距,每列的个数以及内间距的赋值(如果没有值传入的话,会使用默认值)

在继承于UICollectionViewFlowLayout的QHLFlowLayout.m文件中

定义一些宏定义

#define kColumnCount [self numberOfItemColumn]
#define kMargin [self marginBetweenItems]
#define kSectionInset [self sectionInsetOfFlowLayout]
#pragma mark - 返回每行列数
- (NSInteger)numberOfItemColumn {
    NSInteger count = 0;
    
    if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutNumberOfItemColumnCount:)]) {
        
        count = [self.flowLayoutDelegate flowLayoutNumberOfItemColumnCount:self];
    } else {
        count = 3;  //外界不传高度进来的话  默认每行列数是100
    }
    
    return count;
}
#pragma mark - 返回间距
- (CGFloat)marginBetweenItems {
    CGFloat margin = 0;
    if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutMarginBetweenItems:)]) {
        margin = [self.flowLayoutDelegate flowLayoutMarginBetweenItems:self];
    } else {
        margin = 10;  //外界不传高度进来的话  默认间距是100
    }
    return margin;
}
#pragma mark - 设置内边距
- (UIEdgeInsets)sectionInsetOfFlowLayout {
    UIEdgeInsets insets;
    
    if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutSectionInsetOfItems:)]) {
        insets = [self.flowLayoutDelegate flowLayoutSectionInsetOfItems:self];
        
        if (insets.left != insets.right) { // 设置左右边距相同(去左右边距较小值)
            CGFloat min = insets.left < insets.right ? insets.left : insets.right;
            
            insets.left = insets.right = min;
        }
    } else {
        insets = UIEdgeInsetsMake(10, 10, 10, 10);
    }
    
    return insets;
}
通过下面方法,可以获得外部传入的item的高度值
#pragma mark - 获取 item 高度
- (CGFloat)heightForItem:(NSIndexPath *)indexPath itemWithWidth:(CGFloat)width {
    
    CGFloat height = 0;
    
    if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayout:heightForItems:itemWithWidth:)]) {
        height = [self.flowLayoutDelegate flowLayout:self heightForItems:indexPath itemWithWidth:width];
    } else {
        height = 100 + arc4random_uniform(10) * 10; //外界不传高度进来的话  默认随机产生高度
    }
    
    return height;
}
定义一个数组,用来保存最小的Y值,数组的长度依据瀑布流的列数来决定,初始化数组的时候数组的每个元素都赋值为 @0

- (NSMutableArray *)positionArray {
    if (!_positionArray) {
        _positionArray = [NSMutableArray array];
        
        for (int i = 0; i < kColumnCount; i++) {
            _positionArray[i] = @0;
        }
    }
    return _positionArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    CGFloat itemW = (self.collectionView.frame.size.width - kMargin * (kColumnCount - 1) - 2 * kSectionInset.left) / kColumnCount;
    
    //假设数组中第一个就是最小 Y 值
    CGFloat minY = [self.positionArray[0] floatValue];
    
    //设置最小 Y 值的 下标index
    NSInteger indexMinY = 0;
    
    for (int i = 0; i < kColumnCount; i++) {
        CGFloat currentY = [self.positionArray[i] floatValue];
        
        if (minY >= currentY) {
            minY = currentY;
            indexMinY = i;
        }
    }
    
    CGFloat itemX = kSectionInset.left + (kMargin + itemW) * indexMinY;
    
    CGFloat itemH = [self heightForItem:indexPath itemWithWidth:itemW];
    
    CGFloat itemY = minY + kMargin;
    
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    attributes.frame = CGRectMake(itemX, itemY, itemW, itemH);
    
    CGFloat newY = CGRectGetMaxY(attributes.frame);
    
    self.positionArray[indexMinY] = @(newY);
    
    return attributes;
}

在上面该方法中,返回的是每一个item的attribute对象,该方法中主要是计算了item得frame值:

    1> 首先,根据item间距 列数 以及内边距的left值来计算出每个item的宽度itemW

    2> 然后假定最小Y值是数组self.positionArray中的第一个值,最小Y值的下标是数组中self.positionArray中的第一个元素的下标,然后遍历self.positionArray数组,来获取真实的最小Y值以及最小Y值的下标.

    3> 根据上面获得的item宽度itemW,最小Y值下标来计算得出

    4> 对于item的高度,则需要调用方法 [self heightForItem:indexPath itemWithWidth:itemW] 如果外部有高度传入就可以获取到传入的高度,如果没有传入则使用默认的高度.

    5> item的Y值就是刚才遍历获得的最小Y值在加上一个间距kMargin

    6> 然后用类UICollectionViewLayoutAttributes来创建一个attribute对象,对其frame进行赋值,并return attribute返回

    7> 在return之前,把刚遍历获得的最小Y值根据最小Y值的下标,把self.positionArray中对应的数据覆盖掉

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    
    self.positionArray = nil;
    
    NSMutableArray *attributes = [NSMutableArray array];
    
    //组数
    NSInteger sectionCount = [self.collectionView numberOfSections];
    
    //每组item数
    NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
    
    for (int j = 0; j < sectionCount; j++) {
        for (int i = 0; i < itemCount; i++) {
            
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:j];
            
            UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
            
            [attributes addObject:attribute];
        }
    }
    
    return attributes;
}

在上面该方法中:

    1> 在使用该方法时,要注意,要把self.positionArray 置为 空,每次调用该方法时都会去循环调用  layoutAttributesForItemAtIndexPath: 方法,在该方法中要使用到self.positionArray,不置为空的话,数据会出问题.

    2> 通过方法 [self.collectionView numberOfSections] 获取到collectionView得组数.初始化一个可变数组attributes

    3> 循环遍历组数sectionCount:

        3.1> 在该循环中,通过方法 [self.collectionView numberOfItemsInSection:j] 获取到collectionView得每组item数,然后遍历循环每组的item数

        3.2> 再循环中,调用方法 [self layoutAttributesForItemAtIndexPath:indexPath] 获取到每一个item相对应的attribute属性,并添加到可变数组attributes数组中

    4> 循环结束后,return 可变数组attributes 退出方法

#pragma mark - 设置contentSize
- (CGSize)collectionViewContentSize {
    
    //定义最大值 Y
    CGFloat maxY = 0;
    
    if (self.positionArray.count) {
        for (int i = 0; i < self.positionArray.count; i++) {
            CGFloat currentY = [self.positionArray[i] floatValue];
            
            if (maxY < currentY) {
                maxY = currentY;
            }
        }
    }
    return CGSizeMake(0, maxY);
}

在该方法中,是为了获取到collectionView的contentSize : 根据数组self.positionArray保存这的数据,可以获取到所有item中的一个最大Y值,然后就可以获取到contentSize

当items的bounds改变的时候重新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

通过重写这些方法之后,基本可以实现一个自定义的flowLayout了.

在collectionView通过这个自定义的flowLayout 来布局,通过给flowLayout设置代理的方式给flowLayout传值,最终实现简单瀑布流的功能实现.

demo已经上传到了cocoaChina:http://code.cocoachina.com/view/129764

如果有什么不足之处,请帮忙指点下~~

原文地址:https://www.cnblogs.com/qhlbk/p/5246411.html