您的位置:首页 > 移动开发 > Objective-C

瀑布流

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

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

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

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



可以看到,左边的 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];


然后更改 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 哦
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息