一直以来都想研究瀑布流的具体实现方法(原由是由于一则男女程序员应聘的笑话,作程序的朋友应该都知道)。最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其UICollectionViewLayout进行布局是最为简单方便的。但再用其布局以前必须了解其布局原理。为方便你们学习理解此处补上demo地址https://github.com/PurpleSweetPotatoes/CollcetionViewLayout_demogit
在这里笔者挑出其中较为重要的几个方法来进行讲解。程序员
1.- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 当collectionView视图位置有新改变(发生移动)时调用,其若返回YES则从新布局github
2.- (void)prepareLayout 准备好布局时调用。此时collectionView全部属性都已肯定。读者在这里能够将collectionView当作画布,有了画布后,咱们即可以在其上面画出每一个item数组
3.- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 返回collectionView视图中全部视图的属性(UICollectionViewLayoutAttributes)数组布局
4.- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 返回indexPath对应item的属性学习
5.- (CGSize)collectionViewContentSize 设置collectionView的可显示范围测试
这些方法中最重要的即是3,4方法,在3方法中返回全部视图属性数组,并根据这些属性进行布局,而4方法则返回每一个item的属性,咱们则在这里设置每一个item的属性(主要是frame),就可让collectionView按照咱们的意愿进行布局了!(在这里咱们不须要用到1方法,若item属性根据滑动改变,此时就须要随时进行布局改变)atom
瀑布流的实现示意图以下spa
由图示意可看出除开最开始3个item,后面的item都是存放3列中的最短列上面,所以咱们只须要计算出每一个item的frame,并摆放的话那咱们的瀑布流天然就成功了。设计
.h文件中
1 typedef CGFloat(^HeightBlock)(NSIndexPath *indexPath , CGFloat width); 2 @interface BQWaterLayout : UICollectionViewLayout 3 /** 列数 */ 4 @property (nonatomic, assign) NSInteger lineNumber; 5 /** 行间距 */ 6 @property (nonatomic, assign) CGFloat rowSpacing; 7 /** 列间距 */ 8 @property (nonatomic, assign) CGFloat lineSpacing; 9 /** 内边距 */ 10 @property (nonatomic, assign) UIEdgeInsets sectionInset; 11 /** 12 * 计算各个item高度方法 必须实现 13 * 14 * @param block 设计计算item高度的block 15 */ 16 - (void)computeIndexCellHeightWithWidthBlock:(CGFloat(^)(NSIndexPath *indexPath , CGFloat width))block; 17 @end
为了方便修改瀑布流的布局咱们须要设置排列布局的各个接口,由于瀑布流中只能经过列数计算出item的宽,所以须要使用computeIndexCellHeightWithWidthBlock来计算出每一个item的高度(利用宽高比)!
.m文件中
代码中注释已经写的很明白了,无需多作解释,此处写法只能实现item的布局,不能添加headview或footview!
1 @interface BQWaterLayout() 2 /** 存放每列高度字典*/ 3 @property (nonatomic, strong) NSMutableDictionary *dicOfheight; 4 /** 存放全部item的attrubutes属性*/ 5 @property (nonatomic, strong) NSMutableArray *array; 6 /** 计算每一个item高度的block,必须实现*/ 7 @property (nonatomic, copy) HeightBlock block; 8 @end 9 10 @implementation BQWaterLayout 11 - (instancetype)init 12 { 13 self = [super init]; 14 if (self) { 15 //对默认属性进行设置 16 /** 17 默认行数 3行 18 默认行间距 10.0f 19 默认列间距 10.0f 20 默认内边距 top:10 left:10 bottom:10 right:10 21 */ 22 self.lineNumber = 3; 23 self.rowSpacing = 10.0f; 24 self.lineSpacing = 10.0f; 25 self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 26 _dicOfheight = [NSMutableDictionary dictionary]; 27 _array = [NSMutableArray array]; 28 } 29 return self; 30 } 31 32 /** 33 * 准备好布局时调用 34 */ 35 - (void)prepareLayout{ 36 [super prepareLayout]; 37 NSInteger count = [self.collectionView numberOfItemsInSection:0]; 38 //初始化好每列的高度 39 for (NSInteger i = 0; i < self.lineNumber ; i++) { 40 [_dicOfheight setObject:@(self.sectionInset.top) forKey:[NSString stringWithFormat:@"%ld",i]]; 41 } 42 //获得每一个item的属性值进行存储 43 for (NSInteger i = 0 ; i < count; i ++) { 44 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; 45 [_array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; 46 } 47 } 48 /** 49 * 设置可滚动区域范围 50 */ 51 - (CGSize)collectionViewContentSize{ 52 NSLog(@"collectionViewContentSize"); 53 __block NSString *maxHeightline = @"0"; 54 [_dicOfheight enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 55 if ([_dicOfheight[maxHeightline] floatValue] < [obj floatValue] ) { 56 maxHeightline = key; 57 } 58 }]; 59 return CGSizeMake(self.collectionView.bounds.size.width, [_dicOfheight[maxHeightline] floatValue] + self.sectionInset.bottom); 60 } 61 /** 62 * 计算indexPath下item的属性的方法 63 * 64 * @return item的属性 65 */ 66 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ 67 //经过indexPath建立一个item属性attr 68 UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 69 //计算item宽 70 CGFloat itemW = (self.collectionView.bounds.size.width - (self.sectionInset.left + self.sectionInset.right) - (self.lineNumber - 1) * self.lineSpacing) / self.lineNumber; 71 CGFloat itemH; 72 //计算item高 73 if (self.block != nil) { 74 itemH = self.block(indexPath, itemW); 75 }else{ 76 NSAssert(itemH != 0,@"Please implement computeIndexCellHeightWithWidthBlock Method"); 77 } 78 //计算item的frame 79 CGRect frame; 80 frame.size = CGSizeMake(itemW, itemH); 81 //循环遍历找出高度最短行 82 __block NSString *lineMinHeight = @"0"; 83 [_dicOfheight enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 84 if ([_dicOfheight[lineMinHeight] floatValue] > [obj floatValue]) { 85 lineMinHeight = key; 86 } 87 }]; 88 int line = [lineMinHeight intValue]; 89 //找出最短行后,计算item位置 90 frame.origin = CGPointMake(self.sectionInset.left + line * (itemW + self.lineSpacing), [_dicOfheight[lineMinHeight] floatValue]); 91 _dicOfheight[lineMinHeight] = @(frame.size.height + self.rowSpacing + [_dicOfheight[lineMinHeight] floatValue]); 92 attr.frame = frame; 93 94 return attr; 95 } 96 /** 97 * 返回视图框内item的属性,能够直接返回全部item属性 98 */ 99 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ 100 return _array; 101 } 102 /** 103 * 设置计算高度block方法 104 * 105 * @param block 计算item高度的block 106 */ 107 - (void)computeIndexCellHeightWithWidthBlock:(CGFloat (^)(NSIndexPath *, CGFloat))block{ 108 if (self.block != block) { 109 self.block = block; 110 } 111 } 112 @end
至此一个简单的collectionViewLayout瀑布流布局便设置完成,只须要在本身代码中使用此布局即可以获得一个瀑布流了!
下图是笔者的效果图:
2列效果 3列效果
后记:
笔者原本开始还担忧若是item过多,那么设置的属性就会过多,好比数组内存放一千或一万个item的属性,后来通过笔者测试后发现,系统应该每次都是事先计算好了全部item的属性(经过tableView计算每行高度的代理方法来思考),所以直接初始化好全部item的属性作法应该不会有太大弊端!若是笔者所作有什么错误或不妥之处望指出!谢谢!