瀑布流
- 根据上一篇的UICollectionView,现在我们来搭建一个瀑布流的布局。
思路:分三列,用个数组来存放三列的最底部y值,比较哪个最小,那么下一个cell就放哪个位置
框架的思路:自己写个瀑布流的布局框架
- 定义一些成员变量,提供给外界设置
- 这里有四边距、水平间距、垂直间距、默认列数
@property (assign,nonatomic) CGFloat horizontalMargin;
@property (assign,nonatomic) CGFloat verticalMargin;
@property (assign,nonatomic) NSInteger columps;
@property (assign,nonatomic) UIEdgeInsets insets;
- 如果不设置的话,我们有默认值:(注意这个不是view,没有initwithframe方法)
- (instancetype)init{
if (self = [super init]) {
_horizontalMargin = 10;
_verticalMargin = 10;
_columps = 3;
_insets = UIEdgeInsetsMake(10, 10, 10, 10);
}
return self;
}
计算排布
- 在layoutAttributesForElementsInRect方法里计算cell的排布
- 用一个数组bottomYArray来存放各个列最大的y值,元素个数等于collectionView的列数。每次要放新的cell,就查看数组中哪个列y值最小,就放到哪列
- 搞个数组成员属性,懒加载。初始化,给数组中添加若干个元素(等于上部内边距的值,个数等于列数)。注意要先把里面的数据移除,(不这么做的话,重新来到这个方法,高度就从之前的高度往下算了)
- 计算数组中最小值的方法:
- (NSInteger)getShortestTag{
// shortestTag记录是数组中第几个
NSInteger shortestTag = 0;
// shortestY记录最小的Y值
CGFloat shortestY = [self.bottomYArray[0] floatValue];
// 遍历数组,如果有更小的,那么让shortestY等于它,shortestTag等于它对应的i
for (int i = 1; i < self.bottomYArray.count; i++) {
if ([self.bottomYArray[i] floatValue] < shortestY ) {
shortestY = [self.bottomYArray[i] floatValue];
shortestTag = i;
}
}
// 返回告诉外面哪一个的Y值最小
return shortestTag;
}
- layoutAttributesForElementsInRect方法中计算出cell的frame,其中cell的高度暂且用个随机数,cell的y值就是数组bottomYArray中按照上面的方法计算出的位置的元素值。
- 注意`每次计算完一个cell的frame,要用这个cell的Y值替换掉bottomYArray中的对应位置元素的值*
- 具体计算过程和代码的演示在上一篇博客里已经写的很详细了,这里不再赘述。
计算contentSize
- 封装一个方法计算整体最大高度,用来设置contentSize,计算方式同上面计算最小Y值一样
- 在collectionViewContentSize方法里设置好contentSize返回
- 结果程序崩了,告诉我们数组越界。
- 断点排查,发现问题是程序刚启动,会先调用collectionViewContentSize方法。此时数组bottomYArray还是空的,它添加元素的代码在layoutAttributesForElementsInRect方法里。
- 那么,我们在collectionViewContentSize方法里添加这么一段代码就可以了:如果数组是空的,那么就返回个0,0,反正开始拖动它还会来调这个方法来计算的,那时候数组已经有元素了
if (self.bottomYArray.count == 0) {
return CGSizeMake(0, 0);
}
其它bug
- 但是拖动一下会出问题:乱排了,因为方法layoutAttributesForElementsInRect在拖动了就会重新调用,重新计算布局了(具体内部计算方法不明)
- 解决:把计算排布的代码放到- (void)preparelayout方法里,这里只会计算一次;设置成员属性来记录计算好的布局数组(懒加载),在layoutAttributesForElementsInRect方法里直接拿到布局数组返回就可以了
- 注意要把这个布局数组也先清空,理由同上
- preparelayout:第一次显示的时候和reloadDate的时候会调用,而且会比layoutAttributesForElementsInRect先调用
给外界提供cell的高度设置
- 用代理:如果谁成为代理,那么可以实现代理方法给框架传递cell高度,框架内部据此来设置cell的高度
#import <UIKit/UIKit.h>
@class K2SWaterFallLayout;
@protocol K2SWaterfallLayoutDelegate <NSObject>
- (CGFloat)waterfallLayout:(K2SWaterFallLayout *)layout cellHeightWithCellWidth:(CGFloat)cellWidth atIndexPath:(NSIndexPath *)indexPath;
@end
@interface K2SWaterFallLayout : UICollectionViewLayout
@property (weak,nonatomic) id<K2SWaterfallLayoutDelegate> delegate;
- 于是,只要外界成为代理,然后实现这个方法就能给框架传递cell的高度了