一直以来都想研究瀑布流的具体实现方法(起因是因为一则男女程序员应聘的笑话,做程序的朋友应该都知道)。最近学习到了瀑布流的实现方法,瀑布流的实现方式有多种,这里应用collectionView来重写其UICollectionViewLayout进行布局是最为简单方便的。但再用其布局之前必须了解其布局原理。为方便大家学习理解此处补上demo地址https://github.com/PurpleSweetPotatoes/CollcetionViewLayout_demo
在这里笔者挑出其中较为重要的几个方法来进行讲解。
1.- (BOOL)shouldInvalidateLayoutFo
2.- (void)prepareLayout
3.- (NSArray *)layoutAttributesForEleme
4.- (UICollectionViewLayoutAt
5.- (CGSize)collectionViewContentSiz
这些方法中最重要的便是3,4方法,在3方法中返回所有视图属性数组,并根据这些属性进行布局,而4方法则返回每个item的属性,我们则在这里设置每个item的属性(主要是frame),就可以让collectionView按照我们的意愿进行布局了!(在这里我们不需要用到1方法,若item属性根据滑动改变,此时就需要随时进行布局改变)
瀑布流的实现示意图如下
由图示意可看出除开最开始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 16 - (void)computeIndexCellHeightWithWidthBlock:(CGFloat(^)(NSIndexPath *indexPath , CGFloat width))block; 17 @end
为了方便修改瀑布流的布局我们需要设置排列布局的各个接口,因为瀑布流中只能通过列数计算出item的宽,因此需要使用computeIndexCellHeightWi
.m文件中
代码中注释已经写的很明白了,无需多做解释,此处写法只能实现item的布局,不能添加headview或footview!
1 @interface BQWaterLayout() 2 3 @property (nonatomic, strong) NSMutableDictionary *dicOfheight; 4 5 @property (nonatomic, strong) NSMutableArray *array; 6 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 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 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 51 - (CGSize)collectionViewContentSiz e{ 52 NSLog(@"collectionViewContentSiz e"); 53 __block NSString *maxHeightline = @"0"; 54 [_dicOfheight enumerateKeysAndObjectsU singBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 55 if ([_dicOfheight[maxHeightline] floatValue] <<span style="margin: 0px; padding: 0px; font-family: 'Courier New' !important;"> [obj floatValue] ) { 56 maxHeightline = key; 57 } 58 }]; 59 return CGSizeMake(self.collectionView.bounds.size.width, [_dicOfheight[maxHeightline] floatValue] + self.sectionInset.bottom); 60 } 61 66 - (UICollectionViewLayoutAt tributes *)layoutAttributesForItemA tIndexPath:(NSIndexPath *)indexPath{ 67 //通过indexPath创建一个item属性attr 68 UICollectionViewLayoutAt tributes *attr = [UICollectionViewLayoutAt tributes layoutAttributesForCellW ithIndexPath: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 computeIndexCellHeightWi thWidthBlock Method"); 77 } 78 //计算item的frame 79 CGRect frame; 80 frame.size = CGSizeMake(itemW, itemH); 81 //循环遍历找出高度最短行 82 __block NSString *lineMinHeight = @"0"; 83 [_dicOfheight enumerateKeysAndObjectsU singBlock:^(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 99 - (NSArray *)layoutAttributesForEleme ntsInRect:(CGRect)rect{ 100 return _array; 101 } 102 107 - (void)computeIndexCellHeightWi thWidthBlock:(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的属性做法应该不会有太大弊端!如果笔者所做有什么错误或不妥之处望指出!谢谢!