1. 如果系统自带的布局的话,是这样:

    //系统自带的UICollectionViewFlowLayout 而不是UICollectionViewLayout
UICollectionViewFlowLayout *waterLayout = [[UICollectionViewFlowLayout alloc]init];
waterLayout.itemSize = CGSizeMake(, );
waterLayout.minimumLineSpacing = ;
waterLayout.minimumInteritemSpacing = ;
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 = ; i<self.columnsCount; i++) {
NSString *column = [NSString stringWithFormat:@"%d", i];
//NSLog(@"self.sectionInset.top: %f",self.sectionInset.top);//
self.maxYDict[column] = @(self.sectionInset.top);//sectionInset有上下左右
} // 2.计算所有cell的属性
[self.attrsArray removeAllObjects];
//获得有多少个item
NSInteger count = [self.collectionView numberOfItemsInSection:];
for (int i = ; i< count; i++) {
//这个获取属性方法下面有重写
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:]];
[self.attrsArray addObject:attrs];
}
}

上面代码中这部分,为每个item设置属性,然后扔到一个属性数组,所以重点在这里

UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:]];

重写他:

//重写 计算item属性方法 2 思路从需要的值往前推 这个方法最重要
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
// NSLog(@"self.maxYDict %@",self.maxYDict);
// {
// 0 = 30;
// 1 = 30;
// 2 = 30;
// } //假设一个最小值
__block NSString *minColumn = @"";
[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 -)*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 = @"";
[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(, [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

MJ瀑布流学习笔记的更多相关文章

  1. IO流学习笔记(二)之BufferedWriter与BufferedReader及实例Demo

    在之前的学习笔记(http://blog.csdn.net/megustas_jjc/article/details/72853059)中,FileWriter与FileReader的Demo使用的中 ...

  2. JAVA.IO流学习笔记

    一.java.io 的描述 通过数据流.序列化和文件系统提供系统输入和输出.IO流用来处理设备之间的数据传输 二.流 流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数 ...

  3. JAVA输入/输出系统中的其他流学习笔记

    一.字节数组流 字节数组流类能够操作内存中的字节数组,它的数据是一个字节数组.字节数组流类本身适配器设计模式,它把字节数组类型转为流类型使得程序能够对字节数组进行读写操作. 1.ByteArrayIn ...

  4. zkw费用流 学习笔记

    分析 记\(D_i\)为从\(S\)出发到\(i\)的最短路 最短路算法保证, 算法结束时 对于任意存在弧\((i,j)\)满足\(D_i + c_{ij}\ge D_j\) ① 且对于每个 \(j\ ...

  5. IO流学习笔记

    1.File类 文件和目录路径名的抽象表示形式. 4种构造方法 File(File parent, String child) File(File parent, String child) File ...

  6. 最小费用最大流 学习笔记&&Luogu P3381 【模板】最小费用最大流

    题目描述 给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用. 题目链接 思路 最大流是没有问题的,关键是同时保证最小费用,因此,就可以把 ...

  7. IO流学习笔记(一)之FileWriter与FileReader

    IO流用来处理设备之间的数据传输 Java对数据的操作是通过流的方式 Java用于操作流的对象都在IO包中 流按照操作数据分为两种:字节流和字符流 流按流向分为:输入流和输出流 输入流和输出流是相对于 ...

  8. CSS3与页面布局学习笔记(四)——页面布局大全(负边距、双飞翼、多栏、弹性、流式、瀑布流、响应式布局)

    一.负边距与浮动布局 1.1.负边距 所谓的负边距就是margin取负值的情况,如margin:-100px,margin:-100%.当一个元素与另一个元素margin取负值时将拉近距离.常见的功能 ...

  9. web前端学习笔记-瀑布流的算法分析与代码实现

    瀑布流效果目前应用很广泛,像花瓣,新浪轻博,蘑菇街,美丽说等好多网站都有.也有好多支持该效果的前段框架,今天学习了一下这种效果的实现,不依赖插件,自己动手分析实现过程,为了便于叙述清楚,分析中的一些名 ...

随机推荐

  1. 协议:FTP

    ylbtech-协议:FTP FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一.FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客 ...

  2. python-笔记(操作excel)

    python操作excel,python操作excel使用xlrd.xlwt和xlutils模块,xlrd模块是读取excel的,xlwt模块是写excel的,xlutils是用来修改excel的.这 ...

  3. #Week 11 - 343.Integer Break

    Week 11 - 343.Integer Break Given a positive integer n, break it into the sum of at least two positi ...

  4. jdbc步骤:

    一.注册数据库驱动 Class.forName("com.mysql.jdbc.Driver"); 二.建立连接(Connection) Connection conn = Dri ...

  5. C++[Tarjan求点双连通分量,割点][HNOI2012]矿场搭建

    最近在学图论相关的内容,阅读这篇博客的前提是你已经基本了解了Tarjan求点双. 由割点的定义(删去这个点就可使这个图不连通)我们可以知道,坍塌的挖煤点只有在割点上才会使这个图不连通,而除了割点的其他 ...

  6. 安全运维 - Windows系统攻击回溯

    Windows应急事件 病毒.木马.蠕虫 Web服务器入侵事件或第三方服务入侵事件 系统入侵事件 网络攻击事件(DDOS.ARP.DNS劫持等) 通用排查思路 获知异常事件基本情况 发现主机异常现象的 ...

  7. Java基础/发起http和https请求

    Java中发起http和https请求 一般调用外部接口会需要用到http和https请求. 本案例为:前后端完全分离,前端框架(React+Mobx+Nornj),后端(Go语言). 面临问题:跨域 ...

  8. SQL取年月日的不同格式

    Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AM Select CONVERT(varchar(100), GETDATE( ...

  9. Codeforces 191C (LCA+树上差分算法)

    题面 传送门 题目大意: 给出一棵树,再给出k条树上的简单路径,求每条边被不同的路径覆盖了多少次 分析 解决这个问题的经典做法是树上差分算法 它的思想是把"区间"修改转化为左右端点 ...

  10. JS调试分享技巧

    1. 学会使用console.log console.log谁都会用,但是很多同学只知道最简单的console.log(x)这样打印一个对象,当你的代码里面console.log多了之后,会很难将某条 ...