瀑布流是利用自定义的UICollectionViewCell布局去完成的一组UI界面,在iOS开发中,UICollectionView是比较常用的视图控件,必须熟练掌握。
其核心思想是用自定义布局完成对每一列最短的图片的列高进行计算。
其核心算法(写在自定义的布局里)如下:
//计算每一个cell 的位置和大小(核心算法)
-(void)customLayoutCell{
//为每一列的高度赋予初值
for (int i = 0; i < self.numberClumn; i ++) {
self.allHeightColumnArr[i] = @(self.sectionEdgeInsets.top);
}
//得到cell的总个数
NSInteger sumCell = [self.collectionView numberOfItemsInSection:0];
//计算每一个cell 的位置和大小
for (int i = 0; i < sumCell; i++) {
//计算cell的x值
//step1:确定最短列是那一列
NSInteger shortIndex = [self shortColumn];
//step2:计算x值
CGFloat short_X = self.sectionEdgeInsets.left + (self.itemSize.width + self.columSpacing)*shortIndex;
//确定y值
CGFloat short_Y = self.lineSpacing + [self.allHeightColumnArr[shortIndex] floatValue];
//宽度的大小是确定的,不需要计算。self.itemSize.width;
//计算每个高度
CGFloat short_Height = 0.0;
//确定是第几个cell
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//确定是第几个cell,是根据indexPath.item确定从数据源中取值
if (self.delegate && [self.delegate respondsToSelector:@selector(collectionViewWithLayout:indexPath:)]) {
short_Height = [self.delegate collectionViewWithLayout:self indexPath:indexPath];
}
//至此,cell的frame已经确定,将frame的值保存在某个cell的布局属性里。
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(short_X, short_Y, self.itemSize.width, short_Height);
//将每一个布局的属性保存起来,在系统的方法中使用
[self.allAttributeArr addObject:attributes];
//更新当前列的长度
self.allHeightColumnArr[shortIndex] = @(short_Y + short_Height);
}
}
在这里,自定义了两个内部可用的属性,一个用来存储当前所有的列高数据,而另外一个用来存储所有的item的相关信息。
其目的是为了在系统方法里使用,而灭个item的信息都由UICollectionViewLayoutAttributes类来储存(我自己悟出来的,不知正确与否,请看到的大神指正)。
//至此,cell的frame已经确定,将frame的值保存在某个cell的布局属性里。
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(short_X, short_Y, self.itemSize.width, short_Height);
//将每一个布局的属性保存起来,在系统的方法中使用
[self.allAttributeArr addObject:attributes];
然后在自定义的布局里实现以下系统方法:
#pragma mark - 自定义布局的时候需要实现以下系统方法
-(void)prepareLayout{
[super prepareLayout];
//由于是自定义布局,所以需要调用自己的布局方法
[self customLayoutCell];
}
//返回所有cell的布局属性
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.allAttributeArr;
}
//返回某一个cell的布局属性
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
return self.allAttributeArr[indexPath.item];
}
//计算contentSize
-(CGSize)collectionViewContentSize{
//先得到原始的可滑动区域
CGSize size = self.collectionView.contentSize;
//得到最长列的长度
NSInteger index = [self longColumn];
CGFloat height = [self.allHeightColumnArr[index] floatValue];
//更改原始的contentSize的高度.
size.height = height+10;
return size;
}
在核心代码中用到了两个自定义方法,一个用来寻找最短列,方便布局。而另一个用来寻找最长列,确定scrollView的滚动范围。
-(NSInteger)shortColumn{
int index = 0;//用来存储下标
float shortHeight = MAXFLOAT;//用来保存最短列的长度
for (int i = 0; i < self.allHeightColumnArr.count; i++) {
float height = [self.allHeightColumnArr[i] floatValue];
if (height < shortHeight) {
shortHeight = height;
index = i;
}
}
return index;
}
-(NSInteger)longColumn{
int index = 0;//用来存储下标
float longHeight = 0;
for (int i= 0; i <self.allHeightColumnArr.count; i++) {
float height = [self.allHeightColumnArr[i] floatValue];
if (height > longHeight) {
longHeight = height;
index = i;
}
}
return index;
}
那么关键的问题来了,如何按比例计算图片的高呢?
首先,我们在布局里设置代理协议,把计算高度的方法交给代理去做。
然后在根视图控制器里实现其代理,并将根视图控制器设置为布局的代理,利用代理在执行布局的时候执行计算高度的方法。
在根视图控制器的代码如下:
@interface RootViewController ()<UICollectionViewDelegate,UICollectionViewDataSource,WaterfallLayoutDelegate>
@property (nonatomic ,retain)NSArray *allDataAarray;
@end
#pragma mark - 必须实现的代理方法
-(CGFloat)collectionViewWithLayout:(WaterfallLayout *)layout indexPath:(NSIndexPath *)indexPath{
//得到规定好的宽度
float width = layout.itemSize.width;
//得到原始的宽高
NSDictionary *dic = self.allDataAarray[indexPath.item];
float source_width = [[dic objectForKey:@"width"] floatValue];
float source_height = [[dic objectForKey:@"height"] floatValue];
//计算新的高度
float new_height = (source_height/source_width)*width;
return new_height;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return self.allDataAarray.count;
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"item" forIndexPath:indexPath];
NSDictionary *dic = [self.allDataAarray objectAtIndex:indexPath.item];
//得到图片链接
NSString *URLStr = [dic objectForKey:@"thumbURL"];
[cell.myImageView sd_setImageWithURL:[NSURL URLWithString:URLStr]];
return cell;
}
其中,第一个代理方法是自定义的计算行高的方法,其他的dialing方法是UICollectionView的方法。
在自定义布局中的代码如下:
//协议传值的作用为,将rootVC中计算好的cell 的高度传递给本类,用来确定正在布局cell的y值和height,并且我们需要计算最短列,也需要cell的高度
@class WaterfallLayout;
@protocol WaterfallLayoutDelegate <NSObject>
/**
* 方法描述
*
* @param layout 当前类对象传递给rootVC,通过该对象可已在rootVC中得到cell的宽度
* @param indexPath 每个cell的位置传递给rootVC;
*
* @return 返回计算出来的高度
*/
-(CGFloat)collectionViewWithLayout:(WaterfallLayout*)layout indexPath:(NSIndexPath*)indexPath;
@end
在自定义布局中,还需要定义一些属性,方便布局。
代码如下:
@interface WaterfallLayout : UICollectionViewLayout
@property(nonatomic, assign)NSInteger numberClumn;//列的数量
@property (nonatomic, assign)CGSize itemSize;//cell 的大小
@property (nonatomic, assign)CGFloat columSpacing;//列间距
@property (nonatomic, assign)CGFloat lineSpacing;//行间距
@property (nonatomic, assign)UIEdgeInsets sectionEdgeInsets;//分区间距
//代理对象的属性
@property (nonatomic, assign)id<WaterfallLayoutDelegate>delegate;
@end
我把demo上传了,希望值得参考。