瀑布流

之前师父让我写瀑布流,当时虽然照葫芦画瓢写出来了一个,但是并没有完全理解,今天再重新看了一遍,基本都弄懂了,记录一下。


可能有的人会问:“什么是瀑布流?”

就是比如你在淘宝上买东西时,各种商品参差不齐的展现出来。


还是老规矩,先举个栗子。这是不用瀑布流的效果:

瀑布流_第1张图片


可以看到,左边的 cell 中的内容有两行,但是右边的 cell 中的内容只有一行,所以就会导致右边的 cell 的上面和下面多出来一段距离,这样就会显得很丑。


然后我们来看看用了瀑布流的效果:

瀑布流_第2张图片


一看就懂了吧。

然后我们来说说这是怎么实现的。为了更直观,我们先不说代码,直接看效果图,代码后面再说。


首先,找到高度最小的那一列

这是什么意思呢?例如上面的图中,共有两列。我再举一个三列的栗子吧:

假设是三列的,现在已经有了三个 cell(编号为1、2、3),如图所示(手绘,丑就丑吧):

(PS:我用的mac,用的是一个叫 Paintbrush 的软件,功能类似 windows 里的画图)

瀑布流_第3张图片

那么高度最小的一列就是中间那列。


然后,计算当前 cell(此时是第四个 cell)的 frame,然后把这个 cell 加进高度最小的那一列(此时是第二列),结果如图:

瀑布流_第4张图片

同理,轮到第五个 cell 的时候,高度最小的一列就是第一列了,那么就把第五个 cell 放在第一列,以此类推。


没了,就这么简单。用一句话概括瀑布流,就是我们自己布置每一个 cell,让它们错落有致。


下面我们就来具体说说实现过程。


首先,怎么找到高度最小的一列?

定义一个可变数组 cellHeightArray,用于存放每一列的高度。

NSInteger column = 0;
float shortHeight = [[_cellHeightArray objectAtIndex:column] floatValue];
//找出高度最小的列
for (int i = 0; i < _cellHeightArray.count; i++) {
    float height = [[_cellHeightArray objectAtIndex:i] floatValue];
    if (height < shortHeight) {
        shortHeight = height;
        column = i;
    }
}
column 和 shortHeight 是临时变量,用来记录高度最小的列的编号和高度。


然后,怎么计算当前 cell 的 frame?

定义一个代理,遵守 UICollectionViewFlowLayoutDelegate 协议。

@property (nonatomic, assign) id delegate;


然后通过协议获取 cell 的间隙和大小

// 通过协议获得cell的间隙
UIEdgeInsets edgeInsets = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:indexPath.row];
// 通过协议获得cell的大小
CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];

然后更改 cell 的 frame,存进字典中。别忘了更新当前列的高度:
float top = [[_cellHeightArray objectAtIndex:column] floatValue];
//确定cell的frame
CGRect frame = CGRectMake(edgeInsets.left + column * (edgeInsets.left + itemSize.width), top + edgeInsets.top, itemSize.width, itemSize.height);
//更新列高
[_cellHeightArray replaceObjectAtIndex:column withObject:[NSNumber numberWithFloat:top + edgeInsets.top + itemSize.height]];
//每个cell的frame对应一个indexPath,放入字典中
[_cellAttributeDict setObject:NSStringFromCGRect(frame) forKey:indexPath];


返回 cell 布局信息的时候有一个优化。如果一次性返回所有 cell 的布局信息的话可能会导致性能很差,比如说有很多图片的时候。其实我们只需要返回当前能看到的 cell 就行了。当下滑或者上滑的时候再返回其它 cell。

那具体该怎么做呢?只要判断当前的 cell 是否包含在指定的 rect 内即可,是的话就存在数组中:

//返回cell的布局信息,如果忽略传入的rect一次性将所有的cell布局信息返回,图片过多时性能会很差
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSMutableArray *muArr = [NSMutableArray array];
    for (NSIndexPath *indexPath in _cellAttributeDict) {
        CGRect cellRect = CGRectFromString(_cellAttributeDict[indexPath]);
        if (CGRectIntersectsRect(cellRect, rect)) {
            UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
            [muArr addObject:attributes];
        }
    }
    return muArr;
}

layoutAttributesForItemAtIndexPath: 方法如下:

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attributes.frame = CGRectFromString(_cellAttributeDict[indexPath]);
    return attributes;
}

最后要返回 contentSize,遍历存放列高的数组,找到最高列的高度即可:

- (CGSize)collectionViewContentSize {
    CGSize size = self.collectionView.frame.size;
    float maxHeight = 0;
    // 查找最高的列的高度
    for (int i = 0; i < _cellHeightArray.count; i++) {
        float colHeight = [[_cellHeightArray objectAtIndex:i] floatValue];
        if (colHeight > maxHeight) {
            maxHeight = colHeight;
        }
    }
    size.height = maxHeight;
    return size;
}

OK,大功告成!最后只要在设置 collectionView 的 flowLayout 的时候,把它设置成我们自定义的 flowLayout 就行了:

LZNCollectionViewFlowLayout *flow = [[LZNCollectionViewFlowLayout alloc] init];
flow.delegate = self;
_myCollectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:flow];

完整源码见我的github:https://github.com/963239327/UICollectionViewFlowLayout- 别忘了点击右上角的 star 哦






你可能感兴趣的:(iOS,瀑布流,iOS,Objective-C,flowLayout)