之前师父让我写瀑布流,当时虽然照葫芦画瓢写出来了一个,但是并没有完全理解,今天再重新看了一遍,基本都弄懂了,记录一下。
可能有的人会问:“什么是瀑布流?”
就是比如你在淘宝上买东西时,各种商品参差不齐的展现出来。
还是老规矩,先举个栗子。这是不用瀑布流的效果:
可以看到,左边的 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<UICollectionViewDelegateFlowLayout> 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];