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. JS - 创建只读属性

    一:为私有变量创建get()方法 这种方式可以创建 "伪 "只读属性.这并不是一种好方法,因为使用_函数_获得只读的_属性_不太符合一般的逻辑. /** * Represent a ...

  2. 用Vue来实现音乐播放器(九):歌单数据接口分析

    z这里如果我们和之前获取轮播图的数据一样来获取表单的数据  发现根本获取不到 原因是qq音乐在请求头里面加了authority和refer等 但是如果我们通过jsonp实现跨域来请求数据的话  是根本 ...

  3. ELK Stack 企业级日志收集平台

    ELK Stack介绍 大型项目,多产品线的日志收集 ,分析平台 为什么用ELK? 1.开发人员排查问题,服务器上查看权限 2.项目多,服务器多,日志类型多 ELK 架构介绍 数据源--->lo ...

  4. PHP 的源码编译安装

    PHP 架构和安装扩展的几种方式 PHP 三大模块: SAPI:接受并处理请求. Zend Engine:PHP 的核心,负责分析 PHP 代码并转为 opcode,然后在 Zend VM 虚拟机上执 ...

  5. Iview 启动报错 TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

    解决 fs.write(fd, buf, 0, buf.length, 0, function(err, written, buffer) {}); 替换为 fs.write(fd, buf, 0, ...

  6. Java ——方法

    本节重点思维导图  方法的定义 例题:1!+2!+3!+4!+…..+15!=? public class Demo { public static void main(String[] args) ...

  7. Win10.更新

    1.资料: win10怎么关闭自动更新?win10关闭自动更新步骤-太平洋IT百科.html(https://product.pconline.com.cn/itbk/software/win10/1 ...

  8. C# DropDownList绑定添加新数据的三种方法

    一.在前台手动绑定 <asp:DropDownList ID="DropDownList1" runat="server">    <asp: ...

  9. 排序算法六:计数排序(Counting sort)

    前面介绍的几种排序算法,都是基于不同位置的元素比较,算法平均时间复杂度理论最好值是θ(nlgn). 今天介绍一种新的排序算法,计数排序(Counting sort),计数排序是一个非基于比较的线性时间 ...

  10. web框架的本质(使用socket实现的最基础的web框架、使用wsgiref实现的web框架)

    import socket def handle_request(client): data = client.recv(1024) client.send("HTTP/1.1 200 OK ...