上篇博客的实例是自带的UICollectionViewDelegateFlowLayout布局基础上来做的Demo, 详情请看《iOS开发之窥探UICollectionViewController(二) --详解CollectionView各种回调》。UICollectionView之所以强大,是因为其具有自定义功能,这一自定义就不得了啦,自由度非常大,定制的高,所以功能也是灰常强大的。本篇博客就不使用自带的流式布局了,我们要自定义一个瀑布流。自定义的瀑布流可以配置其参数: 每个Cell的边距,共有多少列,Cell的最大以及最小高度是多少等。

  

一.先入为主

先来看一下不同配置参数下运行后的效果吧,每张截图的列数和Cell之间的边距都有所不同,瀑布流的列数依次为2,3,8。有密集恐惧证的童鞋就不要看这些运行效果图了,真的会看晕的。下面这些运行效果就是修改不同的配置参数来进行布局的。看图吧,关于瀑布流的效果就不啰嗦了。以下的效果就是使用自定义布局做的,接下来将会介绍一下其实现原理。

  

二. UICollectionViewLayout

在介绍上述效果实现原理之前,需要介绍一下UICollectionViewLayout。UICollectionView的自定义功能就是自己去实现UICollectionViewLayout的子类,然后重写相应的方法来实现Cell的布局,先介绍一下需要重写的方法,然后再此方法上进行应用实现上述瀑布流。好,废话少说,干活走起。

    

1.布局预加载函数

当布局首次被加载时会调用prepareLayout函数,见名知意,就是预先加载布局,在该方法中可以去初始化布局相关的数据。该方法类似于视图控制器的ViewDidLoad方法,稍后回用到该方法。

1 // The collection view calls -prepareLayout once at its first layout as the first message to the layout instance.
2 // The collection view calls -prepareLayout again after layout is invalidated and before requerying the layout information.
3 // Subclasses should always call super if they override.
4 - (void)prepareLayout;

    

2.内容滚动范围

下方是定义ContentSize的方法。该方法会返回CollectionView的大小,这个方法也是自定义布局中必须实现的方法。说白了,就是设置ScrollView的ContentSize,即滚动区域。

1 // Subclasses must override this method and use it to return the width and height of the collection view’s content. These values represent the width and height of all the content, not just the content that is currently visible. The collection view uses this information to configure its own content size to facilitate scrolling.
2 - (CGSize)collectionViewContentSize;

3.确定布局属性

下方四个方法是确定布局属性的,下方第一个方法返回一个数组,该数组中存放的是为每个Cell绑定的UICollectionViewLayoutAttributes属性,便于在下面第二个方法中去定制每个Cell的属性。第三个方法就是根据indexPath来获取Cell所绑定的layoutAtrributes, 然后去更改UICollectionViewLayoutAttributes对象的一些属性并返回,第四个是为Header View或者FooterView来定制其对应的UICollectionViewLayoutAttributes,然后返回。

1 // UICollectionView calls these four methods to determine the layout information.
2 // Implement -layoutAttributesForElementsInRect: to return layout attributes for for supplementary or decoration views, or to perform layout in an as-needed-on-screen fashion.
3 // Additionally, all layout subclasses should implement -layoutAttributesForItemAtIndexPath: to return layout attributes instances on demand for specific index paths.
4 // If the layout supports any supplementary or decoration view types, it should also implement the respective atIndexPath: methods for those types.
5 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect
6 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
7 - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
8 - (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;

4.UICollectionViewLayoutAttributes

下方是UICollectionViewLayoutAttributes常用的属性,你可以在上面第二个方法中去为下方这些属性赋值,为Cell定制属于自己的Attributes。由下方的属性就对自定义布局的的强大,在本篇博客中只用到了下方的一个属性,那就是frame。

1 @property (nonatomic) CGRect frame;
2 @property (nonatomic) CGPoint center;
3 @property (nonatomic) CGSize size;
4 @property (nonatomic) CATransform3D transform3D;
5 @property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
6 @property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
7 @property (nonatomic) CGFloat alpha;
8 @property (nonatomic) NSInteger zIndex; // default is 0
9 @property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES

  

三. UICollectionViewLayout的应用

经过上面的简单介绍,想必对UICollectionViewLayout有一定的了解吧,UICollectionViewLayout中还有好多方法,以后用到的时候在给大家介绍。接下来要使用自定义布局来实现瀑布流。我们需要在UICollectionViewLayout的子类中实现相应的布局方法,因为UICollectionViewLayout是虚基类,是不能直接被实例化的,所以我们需要新建一个布局类,这个布局类继承自UICollectionViewLayout。然后去实现上述方法,给每个Cell定制不同的UICollectionViewLayoutAttributes。好了还是拿代码说话吧。

    

1.重写prepareLayout方法去初始化一些数据,该方法在CollectionView重新加载时只会调用一次,所以把一些参数的配置,计算每个Cell的宽度,每个Cell的高度等代码放在预处理函数中。在该函数中具体调用的函数如下所示:

 1 #pragma mark -- <UICollectionViewLayout>虚基类中重写的方法
2
3 /**
4 * 该方法是预加载layout, 只会被执行一次
5 */
6 - (void)prepareLayout{
7 [super prepareLayout];
8
9 [self initData];
10
11 [self initCellWidth];
12
13 [self initCellHeight];
14
15 }

2.返回内容的范围,即为CollectionView设定ContentSize。ContentSize的Width就是屏幕的宽度,而ContentSize的高度是一列中最后一个Cell的Y坐标加上其自身高度的最大值。在此函数中会调用求CellY数组中的最大值。具体实现代码如下:

1 /**
2 * 该方法返回CollectionView的ContentSize的大小
3 */
4 - (CGSize)collectionViewContentSize{
5
6 CGFloat height = [self maxCellYArrayWithArray:_cellYArray];
7
8 return CGSizeMake(SCREEN_WIDTH, height);
9 }

3.下面的方法是为每个Cell去绑定一个UICollectionViewLayoutAttributes对象,并且以数组的形式返回,在我们的自定义瀑布流中,我们只自定义了Cell的frame,就可以实现我们的瀑布流,UICollectionViewLayoutAttributes的其他属性我们没有用到,由此可以看出自定义Cell布局功能的强大。

 1 /**
2 * 该方法为每个Cell绑定一个Layout属性~
3 */
4 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
5 {
6
7 [self initCellYArray];
8
9 NSMutableArray *array = [NSMutableArray array];
10
11 //add cells
12 for (int i=0; i < _numberOfCellsInSections; i++)
13 {
14 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
15
16 UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
17
18 [array addObject:attributes];
19 }
20
21 return array;
22
23 }

4. 通过下述方法设定每个Cell的UICollectionViewLayoutAttributes对象的参数,为了实现瀑布流所以我们只需要设置每个Cell的frame即可。每个cell的frame的确定是以列来定的,有所在列的上个Cell的Y坐标来确定下个cell的位置。瀑布流实现关键点如下:

   (1)Cell宽度计算:如果瀑布流的列数和Cell的Padding确定了,那么每个Cell的宽度再通过屏幕的宽度就可以计算出来了。

   (2)Cell高度计算:通过随机数生成的高度

   (3)Cell的X轴坐标计算:通过列数,和Padding,以及每个Cell的宽度很容易就可以计算出每个Cell的X坐标。

   (4)Cell的Y轴坐标计算:通过Cell所在列的上一个Cell的Y轴坐标,Padding, 和 上一个Cell的高度就可以计算下一个Cell的Y坐标,并记录在Y坐标的数组中了。

/**
* 该方法为每个Cell绑定一个Layout属性~
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; CGRect frame = CGRectZero; CGFloat cellHeight = [_cellHeightArray[indexPath.row] floatValue]; NSInteger minYIndex = [self minCellYArrayWithArray:_cellYArray]; CGFloat tempX = [_cellXArray[minYIndex] floatValue]; CGFloat tempY = [_cellYArray[minYIndex] floatValue]; frame = CGRectMake(tempX, tempY, _cellWidth, cellHeight); //更新相应的Y坐标
_cellYArray[minYIndex] = @(tempY + cellHeight + _padding); //计算每个Cell的位置
attributes.frame = frame; return attributes;
}

5. initData方法主要是对数据进行初始化,在本篇博客中为了先实现效果,我们暂且把数据给写死。下篇博客会在本篇博客中的基础上进行优化和改进,这些配置参数都会在Delegate中提供,便于灵活的去定制属于你自己的瀑布流。本篇博客中Demo的配置项先写死就OK了,还是那句话,下篇博客中会给出一些相应的代理,来定制我们的瀑布流。

/**
* 初始化相关数据
*/
- (void) initData{
_numberOfSections = [self.collectionView numberOfSections];
_numberOfCellsInSections = [self.collectionView numberOfItemsInSection:0]; //通过回调获取列数
_columnCount = 5;
_padding = 5;
_cellMinHeight = 50;
_cellMaxHeight = 150;
}

6.下方的方法是根据Cell的列数来求出Cell的宽度。因为Cell的宽度都是一样的,每个Cell的间隔也是一定的。例如有5列Cell, 那么Cell中间的间隔就有4(5-1)个,那么每个Cell的宽度就是屏幕的宽度减去所有间隔的宽度,再除以列数就是Cell的宽度。如果没听我啰嗦明白的话,直接看代码吧,并不复杂。每个Cell的宽度和间隔确定了,那么每个Cell的X轴坐标也就确定了。代码如下:

 1 /**
2 * 根据Cell的列数求出Cell的宽度
3 */
4 - (void) initCellWidth{
5 //计算每个Cell的宽度
6 _cellWidth = (SCREEN_WIDTH - (_columnCount -1) * _padding) / _columnCount;
7
8 //为每个Cell计算X坐标
9 _cellXArray = [[NSMutableArray alloc] initWithCapacity:_columnCount];
10 for (int i = 0; i < _columnCount; i ++) {
11
12 CGFloat tempX = i * (_cellWidth + _padding);
13
14 [_cellXArray addObject:@(tempX)];
15 }
16
17 }

6. 根据Cell的最小高度和最大高度来利用随机数计算每个Cell的高度,把每个Cell的高度记录在数组中,便于Cell加载时使用。具体代码如下:

 1 /**
2 * 随机生成Cell的高度
3 */
4 - (void) initCellHeight{
5 //随机生成Cell的高度
6 _cellHeightArray = [[NSMutableArray alloc] initWithCapacity:_numberOfCellsInSections];
7 for (int i = 0; i < _numberOfCellsInSections; i ++) {
8
9 CGFloat cellHeight = arc4random() % (_cellMaxHeight - _cellMinHeight) + _cellMinHeight;
10
11 [_cellHeightArray addObject:@(cellHeight)];
12 }
13
14 }

7.初始化Cell的Y轴坐标数组,因为是瀑布流,瀑布流的特点是每列中Cell的X轴坐标是相同的,我们只需要根据本列上一个Cell的Y轴坐标来确定本列中将要插入Cell的Y轴坐标,所有我们需要维护一个每列当前Cell的Y轴坐标数组。其初始化方法如下:

/**
* 初始化每列Cell的Y轴坐标
*/
- (void) initCellYArray{
_cellYArray = [[NSMutableArray alloc] initWithCapacity:_columnCount]; for (int i = 0; i < _columnCount; i ++) {
[_cellYArray addObject:@(0)];
}
}

8.下面的方法是求Cell的Y轴坐标数组的最大值,因为在Cell都加载完毕后,Cell数组中最大值就是CollectionView的ContentSize的Height的值。

 1 /**
2 * 求CellY数组中的最大值并返回
3 */
4 - (CGFloat) maxCellYArrayWithArray: (NSMutableArray *) array{
5 if (array.count == 0) {
6 return 0.0f;
7 }
8
9 CGFloat max = [array[0] floatValue];
10 for (NSNumber *number in array) {
11
12 CGFloat temp = [number floatValue];
13
14 if (max < temp) {
15 max = temp;
16 }
17 }
18
19 return max;
20 }

  

9.下方代码是求CellY数组中的第一个最小值的索引,因为求出这个CellY数组中的第一个Cell最新值得索引就是Cell应该插入的列。

 1 /**
2 * 求CellY数组中的最小值的索引
3 */
4 - (CGFloat) minCellYArrayWithArray: (NSMutableArray *) array{
5
6 if (array.count == 0) {
7 return 0.0f;
8 }
9
10 NSInteger minIndex = 0;
11 CGFloat min = [array[0] floatValue];
12
13 for (int i = 0; i < array.count; i ++) {
14 CGFloat temp = [array[i] floatValue];
15
16 if (min > temp) {
17 min = temp;
18 minIndex = i;
19 }
20 }
21
22 return minIndex;
23 }

自定义集合视图控制器布局第一阶段就先到这,下篇博客会在此基础上进一步开发。把上述写死的配置参数,通过Delegate提供,使其在UICollectionView可进行配置,其配置方式类似于UICollectionViewDelegateFlowLayout的代理方法。

上述代码gitHub分享地址:https://github.com/lizelu/CustomCollectionViewLayout

作者:青玉伏案 
出处:http://www.cnblogs.com/ludashi/ 
本文版权归作者和共博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 
如果文中有什么错误,欢迎指出。以免更多的人被误导。

iOS UICollectionView(转三)的更多相关文章

  1. iOS开发UI篇—iOS开发中三种简单的动画设置

    iOS开发UI篇—iOS开发中三种简单的动画设置 [在ios开发中,动画是廉价的] 一.首尾式动画 代码示例: // beginAnimations表示此后的代码要“参与到”动画中 [UIView b ...

  2. XMPPFrameWork IOS 开发(三)登录

    原始地址:XMPPFrameWork IOS 开发(三) XMPP中常用对象们: XMPPStream:xmpp基础服务类 XMPPRoster:好友列表类 XMPPRosterCoreDataSto ...

  3. iOS UICollectionView 长按移动cell

    ref:http://www.jianshu.com/p/31d07bf32d62 iOS 9之后: 示例如下 效果 前言: 看完你可以学到哪些呢? 就是文章标题那么多, 只有那么多. . 手残效果图 ...

  4. iOS UICollectionView(转一) XIB+纯代码创建:cell,头脚视图 cell间距

    之前用CollectionViewController只是皮毛,一些iOS从入门到精通的书上也是泛泛而谈.这几天好好的搞了搞苹果的开发文档上CollectionViewController的内容,亲身 ...

  5. iOS 拨打电话三种方法

    小弟查了很多地方的关于iOS程序拨打电话,大都不全,今天我总结了三种方法,各有不同,拿来给大家分享,希望给大家有所帮助1,这种方法,拨打完电话回不到原来的应用,会停留在通讯录里,而且是直接拨打,不弹出 ...

  6. iOS UICollectionView的实现

    ios的UICollectionView并不能在iOS6之前的版本中使用,为了兼容之前的版本需要自定义UICollectionView.写完之后发现人家已经有开源了,下过来看了看发现我是用UIScro ...

  7. js 与ios 交互的三种方法

    第一种:IOS拦截url  实现跳转 参考链接:http://www.cnblogs.com/pengyingh/articles/2354381.html IOS9.0 及以上支持 第二种:IOS ...

  8. IOS常见的三种回调方法介绍

    认识下三种IOS常见的回调模式. 代理模式作为IOS中最常见的通讯模式,代理几乎无处不在. 这里有一个数组,我们首先通过代理的方式将数组传递到其他方法中去. 设置协议及方法 @protocol Cal ...

  9. 从零开始学ios开发(三):第一个有交互的app

    感谢大家的关注,也给我一份动力,让我继续前进.有了自己的家庭有了孩子,过着上有老下有小的生活,能够挤出点时间学习真的很难,每天弄好孩子睡觉已经是晚上10点左右了,然后再弄自己的事情,一转眼很快就到12 ...

随机推荐

  1. 在网页标题栏title加入图标?

    方法一:制作一个ico格式的图片,将图片命名为favicon.ico,像素大小为16*16,所使用的颜色不得超过16色,然后再把favicon.ico放到网站的根目录下就行了.这样的话浏览器会不停的搜 ...

  2. WebService的基本介绍

    一.WebService的基本介绍    1.WebService是什么? WebService ---> Web Service web的服务    2.思考问题: WebService是we ...

  3. centos7下部署Django(nginx+uwsgi+python3+django)

    系统版本 centos7 python版本 使用官方python3.6.3正式版 django版本 使用本文发布时最新的1.11.7 uwsgi版本 使用本文发布时最新的2.0.15 nginx版本 ...

  4. 状态机编程思想(2):删除代码注释(目前支持C/C++和Java)

    有时为了信息保密或是单纯阅读代码,我们常常需要删除注释. 之前考虑过正则表达式,但是感觉实现起来相当麻烦.而状态机可以把多种情况归为一类状态再行分解,大大简化问题.本文就是基于状态机实现的. 删除C/ ...

  5. anaconda spyder异常如何重新启动

    电脑有一次断电,重新启动后anaconda的spyder就打不开了 重新启动spyder方法: 在anaconda安装目录的Scripts文件夹下,shift+右键在此窗口打开命令行,运行spyder ...

  6. 【LintCode·容易】字符串置换

    字符串置换 描述: 给定两个字符串,请设计一个方法来判定其中一个字符串是否为另一个字符串的置换. 置换的意思是,通过改变顺序可以使得两个字符串相等. 样例: "abc" 为 &qu ...

  7. numpy中关于*和dot的区别

    1.numpy乘法运算中"*"是数组元素逐个计算 >>> import numpy as np >>> a = np.array([[2,3], ...

  8. Go基础之--数组和切片

    数组 数组的定义: 数组是具有固定长度并拥有零个或者多个相同数据类型元素的序列 定义一个数组的方法:var 变量名[len] type 例子:var a[5] int //3个整数的数组var a[5 ...

  9. Python操作 Memcache、Redis、RabbitMQ

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...

  10. Android破解学习之路(三)——Android游戏 切水果破解

    经过前两篇破解教程,想必大家也是明白了破解的简单流程了. 先对APP进行试用,了解APP运行的大概流程,之后从APP中找出关键字(一般的关键字差不多都是支付失败),之后使用Androidkiller进 ...