关于简单瀑布流的实现:
瀑布流是基于UICollectionView来实现的,主要通过自定义的一个继承于UICollectionViewFlowLayout的类
首先,实现基本的界面实现.
然后,在自定义的一个继承于UICollectionViewFlowLayout的类QHLFlowLayout中.
瀑布流的实现,主要是重写了4个方法来实现
在继承于UICollectionViewFlowLayout的QHLFlowLayout.h文件中
@protocol QHLFlowLayoutDelegate <NSObject> - (CGFloat)flowLayout:(QHLFlowLayout *)flowLayout heightForItems:(NSIndexPath *)indexPath itemWithWidth:(CGFloat)width; - (NSInteger)flowLayoutNumberOfItemColumnCount:(QHLFlowLayout *)flowLayout; - (CGFloat)flowLayoutMarginBetweenItems:(QHLFlowLayout *)flowLayout; - (UIEdgeInsets)flowLayoutSectionInsetOfItems:(QHLFlowLayout *)flowLayout; @end
以上是自定义的flowLayout的代理方法.
通过代理,来实现对每个item的高度,item的间距,每列的个数以及内间距的赋值(如果没有值传入的话,会使用默认值)
在继承于UICollectionViewFlowLayout的QHLFlowLayout.m文件中
定义一些宏定义
#define kColumnCount [self numberOfItemColumn] #define kMargin [self marginBetweenItems] #define kSectionInset [self sectionInsetOfFlowLayout]
#pragma mark - 返回每行列数 - (NSInteger)numberOfItemColumn { NSInteger count = 0; if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutNumberOfItemColumnCount:)]) { count = [self.flowLayoutDelegate flowLayoutNumberOfItemColumnCount:self]; } else { count = 3; //外界不传高度进来的话 默认每行列数是100 } return count; }
#pragma mark - 返回间距 - (CGFloat)marginBetweenItems { CGFloat margin = 0; if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutMarginBetweenItems:)]) { margin = [self.flowLayoutDelegate flowLayoutMarginBetweenItems:self]; } else { margin = 10; //外界不传高度进来的话 默认间距是100 } return margin; }
#pragma mark - 设置内边距 - (UIEdgeInsets)sectionInsetOfFlowLayout { UIEdgeInsets insets; if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutSectionInsetOfItems:)]) { insets = [self.flowLayoutDelegate flowLayoutSectionInsetOfItems:self]; if (insets.left != insets.right) { // 设置左右边距相同(去左右边距较小值) CGFloat min = insets.left < insets.right ? insets.left : insets.right; insets.left = insets.right = min; } } else { insets = UIEdgeInsetsMake(10, 10, 10, 10); } return insets; }
通过下面方法,可以获得外部传入的item的高度值 #pragma mark - 获取 item 高度 - (CGFloat)heightForItem:(NSIndexPath *)indexPath itemWithWidth:(CGFloat)width { CGFloat height = 0; if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayout:heightForItems:itemWithWidth:)]) { height = [self.flowLayoutDelegate flowLayout:self heightForItems:indexPath itemWithWidth:width]; } else { height = 100 + arc4random_uniform(10) * 10; //外界不传高度进来的话 默认随机产生高度 } return height; }
定义一个数组,用来保存最小的Y值,数组的长度依据瀑布流的列数来决定,初始化数组的时候数组的每个元素都赋值为 @0 - (NSMutableArray *)positionArray { if (!_positionArray) { _positionArray = [NSMutableArray array]; for (int i = 0; i < kColumnCount; i++) { _positionArray[i] = @0; } } return _positionArray; }
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat itemW = (self.collectionView.frame.size.width - kMargin * (kColumnCount - 1) - 2 * kSectionInset.left) / kColumnCount; //假设数组中第一个就是最小 Y 值 CGFloat minY = [self.positionArray[0] floatValue]; //设置最小 Y 值的 下标index NSInteger indexMinY = 0; for (int i = 0; i < kColumnCount; i++) { CGFloat currentY = [self.positionArray[i] floatValue]; if (minY >= currentY) { minY = currentY; indexMinY = i; } } CGFloat itemX = kSectionInset.left + (kMargin + itemW) * indexMinY; CGFloat itemH = [self heightForItem:indexPath itemWithWidth:itemW]; CGFloat itemY = minY + kMargin; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attributes.frame = CGRectMake(itemX, itemY, itemW, itemH); CGFloat newY = CGRectGetMaxY(attributes.frame); self.positionArray[indexMinY] = @(newY); return attributes; }
在上面该方法中,返回的是每一个item的attribute对象,该方法中主要是计算了item得frame值:
1> 首先,根据item间距 列数 以及内边距的left值来计算出每个item的宽度itemW
2> 然后假定最小Y值是数组self.positionArray中的第一个值,最小Y值的下标是数组中self.positionArray中的第一个元素的下标,然后遍历self.positionArray数组,来获取真实的最小Y值以及最小Y值的下标.
3> 根据上面获得的item宽度itemW,最小Y值下标来计算得出
4> 对于item的高度,则需要调用方法 [self heightForItem:indexPath itemWithWidth:itemW] 如果外部有高度传入就可以获取到传入的高度,如果没有传入则使用默认的高度.
5> item的Y值就是刚才遍历获得的最小Y值在加上一个间距kMargin
6> 然后用类UICollectionViewLayoutAttributes来创建一个attribute对象,对其frame进行赋值,并return attribute返回
7> 在return之前,把刚遍历获得的最小Y值根据最小Y值的下标,把self.positionArray中对应的数据覆盖掉
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { self.positionArray = nil; NSMutableArray *attributes = [NSMutableArray array]; //组数 NSInteger sectionCount = [self.collectionView numberOfSections]; //每组item数 NSInteger itemCount = [self.collectionView numberOfItemsInSection:0]; for (int j = 0; j < sectionCount; j++) { for (int i = 0; i < itemCount; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:j]; UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath]; [attributes addObject:attribute]; } } return attributes; }
在上面该方法中:
1> 在使用该方法时,要注意,要把self.positionArray 置为 空,每次调用该方法时都会去循环调用 layoutAttributesForItemAtIndexPath: 方法,在该方法中要使用到self.positionArray,不置为空的话,数据会出问题.
2> 通过方法 [self.collectionView numberOfSections] 获取到collectionView得组数.初始化一个可变数组attributes
3> 循环遍历组数sectionCount:
3.1> 在该循环中,通过方法 [self.collectionView numberOfItemsInSection:j] 获取到collectionView得每组item数,然后遍历循环每组的item数
3.2> 再循环中,调用方法 [self layoutAttributesForItemAtIndexPath:indexPath] 获取到每一个item相对应的attribute属性,并添加到可变数组attributes数组中
4> 循环结束后,return 可变数组attributes 退出方法
#pragma mark - 设置contentSize - (CGSize)collectionViewContentSize { //定义最大值 Y CGFloat maxY = 0; if (self.positionArray.count) { for (int i = 0; i < self.positionArray.count; i++) { CGFloat currentY = [self.positionArray[i] floatValue]; if (maxY < currentY) { maxY = currentY; } } } return CGSizeMake(0, maxY); }
在该方法中,是为了获取到collectionView的contentSize : 根据数组self.positionArray保存这的数据,可以获取到所有item中的一个最大Y值,然后就可以获取到contentSize
当items的bounds改变的时候重新布局 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; }
通过重写这些方法之后,基本可以实现一个自定义的flowLayout了.
在collectionView通过这个自定义的flowLayout 来布局,通过给flowLayout设置代理的方式给flowLayout传值,最终实现简单瀑布流的功能实现.
demo已经上传到了cocoaChina:http://code.cocoachina.com/view/129764
如果有什么不足之处,请帮忙指点下~~