最近在项目中需要实现一个水平的瀑布流(即每个Cell的高度是固定的,但是长度是不固定的),因为需要重写系统

UICollectionViewLayout中的一些方法通过计算去实现手动布局,所以本着代码可复用的原则(其实就是懒,不想再写一遍而已~~),干脆把水平和竖直模式都集成到一个文件中,通过protocol去控制瀑布流的显示模式。

  首先我们需要了解一下UICollectionViewLayout是个啥玩意,为什么通过它可以进行瀑布流的实现呢?在苹果官方Api Reference中我们可以看到,UICollectionViewLayout的作用就是生成集合视图的布局信息。UICollectionViewFlowLayout对UICollectionViewLayout进行了扩充,UICollectionViewFlowLayout不但可以重写页面的布局,而且它还有一些属性可以用于方便的设置诸如cell之间的间距,cell和边界的间距的值,因此我们通过实现一个类继承自UICollectionViewFlowLayout不但可以重写UICollectionViewLayout中的布局方法,还可以快捷的设置cell的间距。接下来我们直接上干货。

  在开始之前我们通过Category来添加一些扩展的方法,以便于我们获取UICollectionView的布局信息。

  1. typedef NS_ENUM(NSInteger, CGYFlowLayoutType)
  2. {
  3.     CGYFlowLayoutTypeHorizontal = 1,                        //水平显示模式,UICollectionView中的元素宽度不同,高度相同
  4.     CGYFlowLayoutTypeVertical,                              //竖直现实模式,UICOllectionView中的元素宽度相同,高度不同
  5. };
  6.  
  7. @protocol CGYFlowLayoutdataSource <NSObject>
  8.  
  9. @required
  10.  
  11. /**
  12.  指定UICollectionView的显示模式
  13.  
  14.  @return CGYFlowLayoutType 瀑布流的展现方式
  15.  */
  16. - (CGYFlowLayoutType)CGYCollectionViewFlowLayoutType;
  17.  
  18. /**
  19.  水平模式下数组中传入的是每个元素的宽度,竖直模式下传入元素的高度
  20.  
  21.  @return 包含Cell高度或宽度的数组
  22.  */
  23. - (NSArray *)CGYFlowLayoutElementsSize;
  24.  
  25. /**
  26.  UICollectionView的宽度
  27.  
  28.  @return UICollectioncView的宽度
  29.  */
  30. - (CGFloat)CGYFlowLayoutWidth;
  31.  
  32. @optional
  33. /**
  34.  Cell高度不定时需要实现此方法,制定CollectionView中显示的列数
  35.  
  36.  @return UICollectionView的列数
  37.  */
  38. - (NSInteger)CGYFlowLayoutVerticalNumber;
  39.  
  40. /**
  41.  Cell宽度不定时需要实现此方法,元素的自定义高度
  42.  
  43.  @return UICollectionView中每个元素的高度
  44.  */
  45. - (CGFloat)CGYFlowLayoutHorizontalCommonHeight;
  46.  
  47. @end
  1. @interface CGYFlowLayout ()
  2. {
  3. NSArray *elementsSize; //布局元素尺寸存储数组
  4. NSInteger verticalNumber; //竖直不规则布局列数
  5. NSMutableArray *_arrayPosition; //元素布局位置存储数组
  6. NSMutableDictionary *_dicVerticalHeight; //对应列高度存储字典
  7.  
  8. CGFloat flowLayoutWidth; //UICollectionView的宽度
  9.  
  10. CGFloat horizontalElementsHeight; //水平模式下每行元素的高度
  11.  
  12. CGFloat HorizontalHeight; //UICollectionView的总高度
  13.  
  14. CGYFlowLayoutType flowLayoutType; //布局模式
  15. }

在声明一些属性用于存储计算相关的数据,准备工作完成以后就可以开始我们的布局实现了。

  首先,UICollectionViewLayout中有一个prepareLayout方法,这个方法在第一次需要布局时立刻被调用,之后在每次布局失效之前被调用,在这个方法里正好适合我们写计算布局信息的逻辑代码。

  1. - (void)prepareLayout
  2. {
  3. [super prepareLayout];
  4.  
  5. [self collectionViewFlowLayoutSource];        
  6.  
  7. switch (flowLayoutType) { //根据布局方式选择进行的方法
  8. case CGYFlowLayoutTypeHorizontal:
  9. {
  10. [self flowLayoutHorizontal];
  11. }
  12. break;
  13. case CGYFlowLayoutTypeVertical:
  14. [self flowLayoutVertical];
  15. }
  16. default:
  17. break;
  18. }
  19. }

  

Cell元素高度相同,宽度不同布局计算方法:

#pragma mark - CGYFlowLayout 布局方法实现

//水平不规则布局方式实现

- (void)flowLayoutHorizontal

{

[_arrayPosition removeAllObjects];

CGFloat positionX = self.sectionInset.left;

HorizontalHeight = self.sectionInset.top;

CGFloat elementsWidth;

for (NSInteger i = 0 ; i < [elementsSize count] ; i++)

{

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];

UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

elementsWidth = [[elementsSize objectAtIndex:i] floatValue];

NSAssert(elementsWidth>flowLayoutWidth, @"这个元素比CollectionView的宽度还宽");

if ((positionX + self.sectionInset.right + elementsWidth) > flowLayoutWidth)

{

positionX = self.sectionInset.left;

HorizontalHeight = HorizontalHeight + horizontalElementsHeight + self.minimumInteritemSpacing;

attr.frame = CGRectMake(positionX, HorizontalHeight, elementsWidth, horizontalElementsHeight);

positionX = positionX + elementsWidth + self.minimumLineSpacing;

}

else

{

attr.frame = CGRectMake(positionX, HorizontalHeight, elementsWidth, horizontalElementsHeight);

positionX = positionX + elementsWidth + self.minimumLineSpacing;

}

[_arrayPosition addObject:attr];

}

HorizontalHeight = HorizontalHeight + horizontalElementsHeight;

}

在Cell元素高度固定时,代码实现较为简单,只需要判断当前行的宽度是否能够容下下一个Cell元素插入就行了,如果下一个元素插入后行宽超过了UICollectionView的宽度,就将Cell插入到下一行,最后计算出布局的总高度信息。在每次插入Cell元素后,通过_arrayPosition数组记录下Cell元素的位置信息。

Cell元素宽度相同,高度不相同时计算相对来说复杂一些,我们需要判断找出每次插入后最短的那一列,将下一个元素插入到最短的列中。我们通过一个NSMutableDictionary来存储每次插入后的列高,NSMutableDictionary的key值就是第几列,value就是该列的高度,在每次插入后都刷新该列的高度值。

  1. //竖直不规则布局方式实现
  2. - (void)flowLayoutVertical
  3. {
  4. [_arrayPosition removeAllObjects]; //重新布局时移除所有
  5.  
  6. CGFloat positionX; //元素布局X坐标
  7. CGFloat positionY; //元素布局Y坐标
  8. CGFloat elementsHeight; //元素高度
  9. NSInteger minHeightPosition; //最小高度行位置
  10.  
  11. _dicVerticalHeight = [[NSMutableDictionary alloc] init];
  12. for(NSInteger i = ; i < verticalNumber ; i++)
  13. {
  14. //初始化高度字典
  15. [_dicVerticalHeight setValue:@"" forKey:[NSString stringWithFormat:@"%ld",(long)i]];
  16. }
  17.  
  18. CGFloat elementsWidth = (flowLayoutWidth - self.minimumLineSpacing*(verticalNumber - )-self.sectionInset.left - self.sectionInset.right + )/verticalNumber;
  19.  
  20. for (NSInteger i = ; i < [elementsSize count] ; i++)
  21. {
  22. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:];
  23.  
  24. UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
  25.  
  26. minHeightPosition = [self MinHeightPosition];
  27. positionX = self.sectionInset.left + (elementsWidth + self.minimumLineSpacing)*minHeightPosition;
  28. positionY =self.sectionInset.top + [[_dicVerticalHeight valueForKey:[NSString stringWithFormat:@"%ld",(long)minHeightPosition]] floatValue];
  29. elementsHeight = [[elementsSize objectAtIndex:i] floatValue];
  30.  
  31. attr.frame = CGRectMake(positionX, positionY, elementsWidth, elementsHeight);
  32.  
  33. [_dicVerticalHeight setValue:[NSNumber numberWithFloat:(positionY + elementsHeight + self.minimumInteritemSpacing)] forKey:[NSString stringWithFormat:@"%ld",(long)minHeightPosition]];
  34.  
  35. [_arrayPosition addObject:attr];
  36. }
  37. }

  当我们在prepareLayout方法中计算出整个UIcollectionView中所有cell的position后,我们可以通过选择重写layoutAttributesForElementsInRect:(CGRect)rect方法返回一个包含所有cell布局信息的数组。

  1. //返回计算好的布局数组信息
  2. - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
  3. {
  4. return _arrayPosition;
  5. }

  现在我们拿到了所有cell的布局信息可以完事了吗?很抱歉,答案是NO!!!UICollectionViewLayout强硬的规定了,当你重写了它的时候,你就必须要实现一个叫做collectionViewContentSize的方法,这个方法确定了UICollectionView可滑动的范围,我试了一下,果然不重写它我们的CollectionView是动不了的。。。

  1. - (CGSize)collectionViewContentSize
  2. {
  3. if (flowLayoutType == CGYFlowLayoutTypeVertical)
  4. {
  5. return CGSizeMake(flowLayoutWidth, [self maxHeightPosition]);
  6. }
  7. else
  8. {
  9. return CGSizeMake(flowLayoutWidth, HorizontalHeight);
  10. }
  11. }

  当UICollectionView的元素高度固定时,返回值就是我们记录下来的HorizontalHeight,但是当元素高度不固定时,我们就要从所有列中找出高度最高的那一列,并返回那一列的高度信息,这样才可以保证在滑动时每个Cell都是可见的。

代码的github地址是:https://github.com/cgy-tiaopi/CGYUICollectionViewFlowLayout 有问题的地方还请大神拍砖指教。

CollectionView水平和竖直瀑布流的实现的更多相关文章

  1. border-radius的水平和竖直半径

    通常我们设置border-radius都只区分四个角的, 如border-radius: 1em 2em. 其实每个角的border-radius都由两部分组成, 水平半径和竖直半径. 要设置水平和竖 ...

  2. 设置UIScrollView只可以水平或者竖直滚动

    UIScrollView里边包含多个UIWebView: 可以通过设置contentSize的值,设置其width为UIScrollerView可视区域的宽度:即UIScrollView的width, ...

  3. Winform中设置ZedGraph曲线图的水平与竖直参考线

    场景 Winforn中设置ZedGraph曲线图的属性.坐标轴属性.刻度属性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...

  4. ListView 水平滑动 水平和竖直滑动

    效果 Activity public class MainActivity extends Activity {     @Override     protected void onCreate(B ...

  5. android实现超酷的腾讯视频首页和垂直水平网格瀑布流一揽子效果

    代码地址如下:http://www.demodashi.com/demo/13381.html 先来一波demo截图 实现ListView.GridView.瀑布流 1.导入RecyclerView的 ...

  6. 用collectionview实现瀑布流-转(后面附demo,供参考)

    算法总体思路 先说一下总体上的思路.既然图片的大小.位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayou ...

  7. 用collectionview实现瀑布流-转

    算法总体思路 先说一下总体上的思路.既然图片的大小.位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayou ...

  8. Android RecyclerView(瀑布流)水平/垂直方向分割线

     Android RecyclerView(瀑布流)水平/垂直方向分割线 Android RecyclerView不像过去的ListView那样随意的设置水平方向的分割线,如果要实现Recycle ...

  9. 【iOS开发】collectionView 瀑布流实现

    一.效果展示 二.思路分析 1> 布局的基本流程 当设置好collectionView的布局方式之后(UICollectionViewFlowLayout),当系统开始布局的时候,会调用 pre ...

随机推荐

  1. Entity Framework 杂记

    本系列文章,将介绍本人在学习和使用Entity Framewrok的过程中的收获与心得. 或许有的地方讲的错误 欢迎大家批评指出. 1.EntityFramework 数据库的迁移 2.Mysql 该 ...

  2. oracle锁等级以及解锁

    以下是两遍关于锁的介绍的文章,第一篇介绍锁等级以及常用操作,第二篇主要介绍了oracle中两个用以查询数据库任意对象的两个视图 一: http://www.cnblogs.com/lguyss/arc ...

  3. codeforces 459E

    codeforces 459E E. Pashmak and Graph time limit per test 1 second memory limit per test 256 megabyte ...

  4. pycharm svn服务器访问不了的问题。

    昨天使用pycharm访问svn服务器的时候一直登陆不上. 总结一下遇到的svn的问题. 1.用户名密码错误.简单致命, 2.svn访问权限,这个是svn服务器那边可以设置的. 3.svn路径拼写错误 ...

  5. Oracle:试图访问正在使用的事务临时表

    处理步骤为 1.找到表ID select * from dba_objects where object_name like 'TPT_RPWORPA1_QRY' 2.通过表ID查找正在使用的事务 s ...

  6. POJ 2226二分图最大匹配

    匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名.匈牙利算法是基于Hall定理中充分性证明的思想,它是二部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图 ...

  7. 【Network】Neutron-Docker-K8S

    Neutron-Docker-K8S openstack/neutron: Neutron is a virtual network service for Openstack. Neutron和SD ...

  8. 我常用的那些linux命令

    我常用的那些linux命令 用linux也有些年头了,说来也忏愧,说是有些年头了,其实也还是个不长进的主.记得第一次接触linux是boss跟我说的怎么操作,什么编辑模式,按i,a,o进入编辑模式.在 ...

  9. MFC2016.6.8

    1.theApp extern声明之后不可以使用?可以用,只是需要extern之后的类名和类名称写对,不要犯得低级错误.2.怎样取出列表控件中的第某列的值?GetItemText(); CListCt ...

  10. MacBook安装双系统(Windows多分区)

    分区 ---------- 启动电脑,放入mac os安装盘,按alt选择光盘启动. 1. 在工具菜单里选择磁盘工具对整个硬盘进行分区: ----------- 第一个是  exFAT.Msdos 格 ...