通过自定义UICollectionViewFlowLayout来实现一个购物网站的图片瀑布流。效果预览图:
CollectionView和流布局的结合,实现图片瀑布流,视觉上看起来非常让人赏心悦目,也让人开启来非常舒服和轻松,瀑布流是一种非常成功地图片展示方式。
流布局最核心的特征是定宽之后,然后等比例(高宽比)缩放图片。我们可以根据屏幕的宽度,特定的图片之间的间距和屏幕与图片之间间距,以及一行当中显示图片的个数来计算宽度;然后根据图片的真实地宽高比例,来计算图片的高度;最后根据图片列之间的间距计算排列的位置。通过上面的描述我们可以计算出图片的frame,然后将frame赋值给流布局中每个item的属性,实现图片瀑布流。
需要注意的是,假如一行当中有3列,那么排列图片的时候,不是根据顺序进行计算排列图片,而是在一行的图片中获取最小的Y值,来优先排列添加图片。
自定义UICollectionViewFlowLayout代码:
#import <UIKit/UIKit.h>
@class WSWaterFallFlowLayout;
@protocol WSWaterFallFlowLayoutDelegate <NSObject>
@required
/**
* 根据当前的item的宽度获取特定索引的高度,图片自身的高度和现有的宽度获取,保证宽高比一定
*
* @param flowLayout 当前的流布局
* @param width 当前的宽度
* @param indexPath 当前索引
*
* @return 根据宽高比算出的高
*/
-(CGFloat)waterFallFlowLayout:(WSWaterFallFlowLayout *)flowLayout itemHeightForItemWidth:(CGFloat)width itemIndexPath:(NSIndexPath *)indexPath;
@end
@interface WSWaterFallFlowLayout : UICollectionViewFlowLayout
/**
* 一行拥有多少个图片,默认值是3
*/
@property (assign, nonatomic) NSInteger columnCount;
/**
* 瀑布流代理
*/
@property (weak, nonatomic) id<WSWaterFallFlowLayoutDelegate> delegate;
@end
//
// WSWaterFallFlowLayout.m
// 瀑布流UICollectionView
//
// Created by WackoSix on 15/11/26.
// Copyright © 2015年 WackoSix. All rights reserved.
//
#import "WSWaterFallFlowLayout.h"
@interface WSWaterFallFlowLayout()
/**
* 保存一行中每个图片的最大Y值
*/
@property (strong, nonatomic) NSMutableDictionary *columnY;
/**
* 当前所有图片的属性
*/
@property (strong, nonatomic) NSMutableArray *itemAttributes;
@end
@implementation WSWaterFallFlowLayout
#pragma mark - 布局设置
// 1.当collectionView中的所有子控件即将显示的时候就会来调用此方法做布局前的准备工作,准备itemSize...等等属性
// 2.当布局的属性发生变化时也会来调用此方法
// 3.当刷新数据之后也会来调用此方法重新做布局前的准备工作
-(void)prepareLayout{
[super prepareLayout];
//清空以前的属性数据(加载新数据)
[self.itemAttributes removeAllObjects];
//初始化字典
for (NSInteger i=0; i<_columnCount; i++) {
NSString *key = [NSString stringWithFormat:@"%ld",i];
self.columnY[key] = @(self.sectionInset.top);
}
//获取所有的数据数
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
//计算每一个item的属性
for (NSInteger i=0; i<itemCount; i++) {
//创建索引
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//获取属性
UICollectionViewLayoutAttributes *attri = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.itemAttributes addObject:attri];
}
//创建footerView的属性
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:1 inSection:0];
UICollectionViewLayoutAttributes *footerAttri = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:indexPath];
footerAttri.frame = CGRectMake(0, [self getMaxColnumY], self.collectionView.bounds.size.width, 50);
[self.itemAttributes addObject:footerAttri];
//添加到最大Y字典
self.columnY[@"0"] = @(CGRectGetMaxY(footerAttri.frame));
}
//返回计算出的属性
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
CGFloat itemW = (self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing *(_columnCount - 1)) / _columnCount;
CGFloat itemH = [self.delegate waterFallFlowLayout:self itemHeightForItemWidth:itemW itemIndexPath:indexPath];
//获取当前的列最大Y,最小的一列
NSString *col = [self getMinColnumYIndex];
CGFloat itemX = self.sectionInset.left + (itemW + self.minimumInteritemSpacing)*col.integerValue;
CGFloat itemY = [self.columnY[col] floatValue];
UICollectionViewLayoutAttributes *attri = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attri.frame = CGRectMake(itemX, itemY, itemW, itemH);
//更新字典
self.columnY[col] = @(itemY + itemH + self.minimumLineSpacing);
return attri;
}
//返回滚动范围
-(CGSize)collectionViewContentSize{
return CGSizeMake(0, [self getMaxColnumY]);
}
//返回所有创建的属性
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.itemAttributes;
}
/**
* 从保存的最大Y的数组中获取最小的key索引
*
* @return key索引
*/
-(NSString *)getMinColnumYIndex{
__block NSString *min = @"0";
[self.columnY enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([obj floatValue] < [self.columnY[min] floatValue]) {
min = key;
}
}];
return min;
}
/**
* 获取最后一行的最大Y
*
* @return 最大Y值
*/
-(CGFloat)getMaxColnumY{
__block CGFloat max = 0;
[self.columnY enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([obj floatValue] > max) {
max = [obj floatValue];
}
}];
return max;
}
#pragma mark - 初始化方法
- (instancetype)init
{
self = [super init];
if (self) {
//设置默认初值
self.columnCount = 3;
self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0);
self.minimumLineSpacing = 10;
self.minimumInteritemSpacing = 30;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
//设置默认初值
self.columnCount = 3;
self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0);
self.minimumLineSpacing = 10;
self.minimumInteritemSpacing = 30;
}
return self;
}
#pragma mark - others
-(NSMutableDictionary *)columnY{
if (_columnY == nil) {
_columnY = [NSMutableDictionary dictionary];
}
return _columnY;
}
-(NSMutableArray *)itemAttributes{
if (_itemAttributes == nil) {
_itemAttributes = [NSMutableArray array];
}
return _itemAttributes;
}
@end