知道 collectionView 的读者都知道,我们在创建 collectionView 的时候,我们就要为 collectionView 添加一个布局
[[UICollectionView alloc] initWithFrame:<#(CGRect)#> collectionViewLayout:<#(nonnull UICollectionViewLayout *)#>]
在这个方法中,我们不得不传入一个 UICollectionViewLayout 的对象进入,而这个对象,就是我们所说的布局,通过这个布局,我们可以实现无限可能的布局,只有你想象不到的,今天,我们来做一做流水布局以下是效果图
一开始,我们要为 collectionView 创建一个布局子类,我们就命名为 ABSWaterflowLayout 吧
@interface ABSWaterFlowLayout : UICollectionViewLayout
@end
之后我们要创建 collectionView 显示的 cell,这个 cell 当然也是继承 uicollectionViewCell 了,因为我们要在里面添加一个图片,这个就要自定义了
#import
@interface ABSCollectionViewCell : UICollectionViewCell
@end
然后我们在 ViewController 中创建我们的主角 collectionView
#import
@interface ViewController : UIViewController
@end
#import "ViewController.h"
@interface ViewController ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic,strong) UICollectionView *collectionView;
/** 数据源*/
@property (nonatomic,strong) NSArray *dataSource;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 创建我们的流水布局
ABSWaterFlowLayout *waterFlowLayout = [[ABSWaterFlowLayout alloc]init];
self.collectionView = [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:waterFlowLayout];
self.collectionView.backgroundColor = [UIColor yellowColor];
// 设置代理
self.collectionView.delegate = self;
// 设置数据源
self.collectionView.dataSource = self;
// 注册 cell
[self.collectionView registerClass:[ABSCollectionViewCell class] forCellWithReuseIdentifier:identifier];
[self.view addSubview:self.collectionView];
// 安全释放
TT_RELEASE_SAFELY(waterFlowLayout);
}
// UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ABSCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor redColor];
if ([self.dataSource[indexPath.row] isKindOfClass:[Shop class]]) {
Shop *shop = self.dataSource[indexPath.row];
cell.shop = shop;
}
return cell;
TT_RELEASE_SAFELY(cell);
}
好了,接下来,我们就安全排布好我们的 ABSWaterflower 布局了,观测我们的效果图,可以看出,每一列和每一行之间都有一个间距的,而这些间距又是让用户可以修改的,为了这个布局以后都能自定义化,我们的设计要更加符和面向对象的特性
#import
@class ABSWaterFlowLayout;
@protocol ABSWaterFlowLayoutDelegate
// 询问它的代理,indexPath 这个位置的 cell 的高度为多少
- (CGFloat)waterflowLayout:(ABSWaterFlowLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath;
@end
@interface ABSWaterFlowLayout : UICollectionViewLayout
/** 每一列的间距*/
@property (nonatomic, assign) NSInteger columnMargin;
/** 每一行的间距*/
@property (nonatomic, assign) NSInteger rowMargin;
/** 每一组的间距*/
@property (nonatomic, assign) UIEdgeInsets sectionInset;
/** 有多少列*/
@property (nonatomic, assign) NSInteger coloumnsCount;
@property (nonatomic, weak) id delegate;
@end
#import "ABSWaterFlowLayout.h"
@interface ABSWaterFlowLayout()
/** 这个字典用来存储每一列最大的 Y 值(每一列的高度)*/
// 如果coloumnsCount只有3列,那么这个 maxDict 就像如此 @{@"0":@(0), @"1":@(0), @"2":@(2)}
@property (nonatomic,strong) NSMutableDictionary *maxDict;
// 这个数组装载着每一个 UICollectionViewcell 的布局属性
@property (nonatomic,strong) NSMutableArray *attrsArray;
@end
@implementation ABSWaterFlowLayout
- (instancetype)init {
if (self = [super init]) {
// 如果用户没有设置这些值,我们必须为用户设置一个默认值
self.columnMargin = 10;
self.rowMargin = 10;
self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
self.coloumnsCount = 3;
}
return self;
}
- (void)dealloc
{
// 安全释放
TT_RELEASE_SAFELY(self.maxDict);
TT_RELEASE_SAFELY(self.attrsArray);
}
#pragma mark - lazy
- (NSMutableDictionary *)maxDict
{
if (!_maxDict ) {
_maxDict = [[NSMutableDictionary alloc]init];
for (int i = 0; i < self.coloumnsCount; i ++) {
NSString *column = [NSString stringWithFormat:@"%d",i];
self.maxDict[column] = @0;
}
}
// @"0" : @(0), @"1" : @(0) , @"2" : @(0) 这种格式来存储这一列最大的 Y 值
return _maxDict;
}
- (NSMutableArray *)attrsArray
{
if (!_attrsArray ) {
_attrsArray = [[NSMutableArray alloc]init];
}
return _attrsArray;
}
// 准备布局的时候先调用这个方法
- (void)prepareLayout {
[super prepareLayout];
for (int i = 0; i < self.coloumnsCount; i ++) {
NSString *column = [NSString stringWithFormat:@"%d",i];
self.maxDict[column] = @(0);
}
// 每次布局之前,先把之前的cell 的布局属性都给删掉,然后重新设置
[self.attrsArray removeAllObjects];
// 第0组的 item 的数量
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];
TT_RELEASE_SAFELY(attrs);
}
}
// 返回内容尺寸
- (CGSize)collectionViewContentSize {
__block NSString *maxColumn = @"0";
// 找出 maxDict 字典中最后最大的行的的高度
[self.maxDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *obj, BOOL * _Nonnull stop) {
if (self.maxDict[column] > self.maxDict[maxColumn] ) {
maxColumn = column;
}
}];
return CGSizeMake(0, [self.maxDict[maxColumn] floatValue]);
}
// 在指定范围内返回每一个 cell 的布局属性
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
// 返回所有布局属性
return self.attrsArray;
}
// 为indexPath 位置中的 cell 返回一个布局属性,告诉这个 cell 改放在哪一个位置(frame)
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// 假设最短的那一列是第0列
__block NSString *minColum = @"0";
// 找出最短的那一列
[self.maxDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL * _Nonnull stop) {
if ([maxY floatValue] < [self.maxDict[minColum] floatValue]) {
minColum = column;
}
}];
// 计算宽度
CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.coloumnsCount - 1) * self.columnMargin ) / self.coloumnsCount;
CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];
// 计算位置
CGFloat x = self.sectionInset.left + (width + self.columnMargin) * [minColum intValue];
CGFloat y = self.rowMargin + [self.maxDict[minColum] floatValue];
// 更新这一列最大的 Y 值
self.maxDict[minColum] = @(y + height);
// 创建属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.frame = CGRectMake(x, y, width, height);
return attrs;
}
好了,至此我们的瀑布流已经完成了,剩下的就只有把 DataSour 的数据远接入,再着就是为ABSWaterFlowLayoutDelegate 返回高度即可以下是我们源代码,欢迎大家查看,(^__^)
http://www.cocoachina.com/bbs/read.php?tid=326153