在实现瀑布流之前,先来介绍下 collectionView 。collectionView 和 tableView 特别的像,但是比 tableView 多了布局。它运用一个灵活多变的布局呈现出你想要的效果。collectionView 用的最多的是用网格来展示数据(如下图所示),当然我们可以自定义布局,实现你能想象到的任何效果。
collectionView 的组成:
效果界面:
原理组成:
从上图中可以看出整个 collectionView 由三种类型的视图组成:
Cell:collectionView 中最主要的展示部分,和 tableView 里的一样。
Supplementary View:展示头视图和尾视图,可以理解为 tableView 中的 sectionHeader 和 sectionFooter。
Decoration View:装饰视图,理解成背景就行了。
与 collectionView 相关联的对象之间的关系
collectionView 的简单使用如下:
//初始化
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *colView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, self.view.height - 64) collectionViewLayout:layout];
colView.backgroundColor = [UIColor grayColor];
[colView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"myCell"];
[colView registerClass:[ZWSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
[colView registerClass:[ZWSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer"];
colView.delegate = self;
colView.dataSource = self;
[self.view addSubview:colView];
代理方法:
#pragma mark UICollectionViewDataSource
//每个section里有多少个cell
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 8;
}
//有多少个section
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 4;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = @"myCell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
cell.backgroundColor = [UIColor yellowColor];
return cell;
}
//headerView和footerView
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier;
if ([kind isEqualToString: UICollectionElementKindSectionFooter]){
reuseIdentifier = @"footer";
} else {
reuseIdentifier = @"header";
}
ZWSupplementaryView *view = [collectionView dequeueReusableSupplementaryViewOfKind :kind withReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]){
view.backgroundColor = [UIColor redColor];
view.label.text = @"header";
} else if ([kind isEqualToString:UICollectionElementKindSectionFooter]){
view.backgroundColor = [UIColor greenColor];
view.label.text = @"footer";
}
return view;
}
#pragma mark UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"section:%ld,index:%ld",indexPath.section,(long)indexPath.row);
}
#pragma mark UICollectionViewDelegateFlowLayout
//定义每个UICollectionView 的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(90, 100);
}
//定义每个UICollectionView的margin
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(5, 5, 5, 5);
}
//每个section中不同的行之间的行间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 5;
}
//每个item之间的间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 5;
}
//返回headerView的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(collectionView.width, 50);
}
//返回footerView的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
return CGSizeMake(collectionView.width, 50);
}
在模拟器 iPhone 6s Plus 运行效果如下:
要想实现瀑布流效果,我们就要自定义 layout 来实现了。
自定义必须要实现下面的方法
collectionViewContentSize
layoutAttributesForElementsInRect:
layoutAttributesForItemAtIndexPath:
shouldInvalidateLayoutForBoundsChange:
layoutAttributesForSupplementaryViewOfKind:atIndexPath: (如果有 supplementary views 需要实现)
layoutAttributesForDecorationViewOfKind:atIndexPath: (如果有 supports decoration views 需要实现)
具体代码如下:
初始化
ZWWaterFlowLayout *layout = [[ZWWaterFlowLayout alloc] init];
layout.columnsCount = 3;
layout.delegate = self;
UICollectionView *colView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, self.view.height - 64) collectionViewLayout:layout];
colView.backgroundColor = [UIColor grayColor];
[colView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"myCell"];
colView.delegate = self;
colView.dataSource = self;
[self.view addSubview:colView];
ZWWaterFlowLayout:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
//返回内容尺寸给UICollectionView
- (CGSize)collectionViewContentSize {
__block NSString *maxColumn = @"0";
[self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
if ([maxY floatValue] > [self.maxYDict[maxColumn] floatValue]) {
maxColumn = column;
}
}];
return CGSizeMake(0, [self.maxYDict[maxColumn] floatValue] + self.sectionInset.bottom);
}
//每次布局之前的准备
- (void)prepareLayout {
[super prepareLayout];
//清空最大的Y值
for (int i = 0; i < self.columnsCount; i++) {
NSString *column = [NSString stringWithFormat:@"%d", i];
self.maxYDict[column] = @(self.sectionInset.top);
}
//计算所有cell的属性
[self.attrsArray removeAllObjects];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i<count; i++) {
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
[self.attrsArray addObject:attrs];
}
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect )rect {
return self.attrsArray;
}
//Item的布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
//假设最短的那一列的第0列
__block NSString *minColumn = @"0";
//找出最短的那一列
[self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
if ([maxY floatValue] < [self.maxYDict[minColumn] floatValue]) {
minColumn = column;
}
}];
//计算尺寸
CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.columnsCount - 1) * self.columnMargin)/self.columnsCount;
CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];;
//计算位置
CGFloat x = self.sectionInset.left + (width + self.columnMargin) * [minColumn intValue];
CGFloat y = [self.maxYDict[minColumn] floatValue] + self.rowMargin;
//更新这一列的最大Y值
self.maxYDict[minColumn] = @(y + height);
//创建属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.frame = CGRectMake(x, y, width, height);
return attrs;
}
通过 ZWWaterFlowLayoutDelegate 返回的高度的现在用的是随机的,在项目中可以让服务端返回图片的宽和高,我们以宽为基准,来计算高度即可。
- (CGFloat)waterflowLayout:(ZWWaterFlowLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath {
int from = 50;
int to = 500;
CGFloat height = (CGFloat)(from + (arc4random() % (to - from + 1)));
NSLog(@"height:%f",height);
return height;
}
运行效果如下:
参考链接: