之前师父让我写瀑布流,当时虽然照葫芦画瓢写出来了一个,但是并没有完全理解,今天再重新看了一遍,基本都弄懂了,记录一下。
可能有的人会问:“什么是瀑布流?”
就是比如你在淘宝上买东西时,各种商品参差不齐的展现出来。
还是老规矩,先举个栗子。这是不用瀑布流的效果:
可以看到,左边的 cell 中的内容有两行,但是右边的 cell 中的内容只有一行,所以就会导致右边的 cell 的上面和下面多出来一段距离,这样就会显得很丑。
然后我们来看看用了瀑布流的效果:
一看就懂了吧。
然后我们来说说这是怎么实现的。为了更直观,我们先不说代码,直接看效果图,代码后面再说。
首先,找到高度最小的那一列。
这是什么意思呢?例如上面的图中,共有两列。我再举一个三列的栗子吧:
假设是三列的,现在已经有了三个 cell(编号为1、2、3),如图所示(手绘,丑就丑吧):
(PS:我用的mac,用的是一个叫 Paintbrush 的软件,功能类似 windows 里的画图)
那么高度最小的一列就是中间那列。
然后,计算当前 cell(此时是第四个 cell)的 frame,然后把这个 cell 加进高度最小的那一列(此时是第二列),结果如图:
同理,轮到第五个 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];
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;
}
- (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;
}
LZNCollectionViewFlowLayout *flow = [[LZNCollectionViewFlowLayout alloc] init];
flow.delegate = self;
_myCollectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:flow];