1. 如果系统自带的布局的话,是这样:
//系统自带的UICollectionViewFlowLayout 而不是UICollectionViewLayout UICollectionViewFlowLayout *waterLayout = [[UICollectionViewFlowLayout alloc]init]; waterLayout.itemSize = CGSizeMake(100, 300); waterLayout.minimumLineSpacing = 5; waterLayout.minimumInteritemSpacing = 5; waterLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
而自定义的话:WaterFlowLayout : UICollectionViewLayout
系统UICollectionViewFlowLayout也是继承自UICollectionViewLayout
2. 主要实现部分:
在- (void)prepareLayout;方法中计算好所有item的布局属性,主要是frame属性,而frame属性包括(x,y, w,h)
因为系统的这个方法
//滚动时是否重新计算 -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ return YES; }
所以- (void)prepareLayout;计算前都要做一次清空:
/** * 每次布局之前的准备 最先调用 */ - (void)prepareLayout{ NSLog(@"prepareLayout");//调一次 // 1.清空最大的Y值 清空是为了防止滚动个计算出问题 for (int i = 0; i<self.columnsCount; i++) { NSString *column = [NSString stringWithFormat:@"%d", i]; //NSLog(@"self.sectionInset.top: %f",self.sectionInset.top);//10 self.maxYDict[column] = @(self.sectionInset.top);//sectionInset有上下左右 } // 2.计算所有cell的属性 [self.attrsArray removeAllObjects]; //获得有多少个item NSInteger count = [self.collectionView numberOfItemsInSection:0]; for (int i = 0 ; i< count; i++) { //这个获取属性方法下面有重写 UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; [self.attrsArray addObject:attrs]; } }
上面代码中这部分,为每个item设置属性,然后扔到一个属性数组,所以重点在这里
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
重写他:
//重写 计算item属性方法 2 思路从需要的值往前推 这个方法最重要 -(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ // NSLog(@"self.maxYDict %@",self.maxYDict); // { // 0 = 30; // 1 = 30; // 2 = 30; // } //假设一个最小值 __block NSString *minColumn = @"0"; [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString * column, id _Nonnull obj, BOOL * _Nonnull stop) { //找到每一行最短的那个列 0/1/2 后面往这个列加下一张图片的height if ([obj floatValue] < [self.maxYDict[minColumn] floatValue]) { minColumn = column; } }]; //计算尺寸 CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.columnsCount -1)*self.columnMargin)/self.columnsCount; //CGFloat height = 100 + arc4random_uniform(100);//先随机给个高度 这样做滚动时会出现烟花缭乱的 CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];//让代理去帮忙计算 //NSLog(@"height %f",height); //计算位置 CGFloat x = self.sectionInset.left + (width + self.columnMargin) *[minColumn intValue]; CGFloat y = [self.maxYDict[minColumn] floatValue] + self.rowMargin; self.maxYDict[minColumn] = @(y + height); // 创建属性 UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attrs.frame = CGRectMake(x, y, width, height); return attrs; }
系统有一个方法会返回所有属性,而返回值是一个数组,数组里装的就是所有item的布局属性,所以重写这个方法:
//重写返回所有item属性方法 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSLog(@"layoutAttributesForElementsInRect"); //在prepare一次性算好 然后扔过来 return self.attrsArray; }
最后整块UICollectionView的content又一个大小需要给出来,重写下面这个方法即可,也就是最大的y+height :
//重写 返回collectionView的 Size方法 这个要计算完所有item才知道 //这个方法子类和父类都会调一次 所以会调2次 不奇怪 -(CGSize)collectionViewContentSize{ //[super prepareLayout]; __block NSString *maxCloumn = @"0"; [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj floatValue] > [self.maxYDict[maxCloumn] floatValue]) { maxCloumn = key; } }]; NSLog(@"collectionViewContentSize - %f",[self.maxYDict[maxCloumn] floatValue]);//一次全部item布局调2次 return CGSizeMake(0, [self.maxYDict[maxCloumn] floatValue]);//0表示不支持水平滚动,最大y值可以遍历 }
另外:因为每个item的高度是要计算的,可以写个私有方法,也可以让ViewController来成为代理,让他帮忙计算。这里用代理实现
//CGFloat height = 100 + arc4random_uniform(100);//先随机给个高度 这样做滚动时会出现烟花缭乱的 CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];//让代理去帮忙计算
利用代理的好处,MJ老师说了:
利用控制器成为自定义布局类的代理,返回想要的高度。利用代理的好处,就是在布局想拿到控制器一些东西的时候,防止布局系统需要的是一个模型时,如果你利用布局一个属性来传值,那么布局系统永远跟模型相关了。为了防止这种依赖关系,所以用代理来做,比较合适,框架类的东西不容易产生对外部代码的依赖。通用型较好。
Demo地址:https://github.com/nwgdegitHub/Waterfalls-flow
另外这边文章实现原理大致相同,值得借鉴。
https://www.jianshu.com/p/995c0f35e7fb