自定义collocationViewLayout实现多区瀑布流

 实现瀑布流简单,实现分区瀑布流,而且每一个区的瀑布流的列数不同且有区头和区尾,就不是太容易了。我嫌麻烦不肯意本身写(——>我认可懒,不肯意动脑子 V)开始在网上找了好多,都是仅仅一个区的瀑布流,没区头和区尾,彻底知足不了个人需求。没办法,产品的需求在那,不能不作吧,因而本身静下心来开始写。git

其代理方法和属性模仿UICollectionViewFlowLayout 所写,使用方法和UICollectionViewFlowLayout相似github

功能描述:数组

  1. 知足UICollectionViewFlowLayout提供的普通的线性布局和网格布局bash

  2. 知足单区和多区的瀑布流布局。函数

  3. 知足多区瀑布流时每一个区的列数能够不一样布局

  4. 知足设置header和footerpost

  5. 知足设置header和footer的间距性能

注意:本文不涉及到装饰视图的相关代理方法以及计算。ui

首先要明白的事情:collectionView与collocationViewLayout的关系。atom

collocationView负责展现,collectionviewLayout负责提供如何展现,包括cell的大小位置,header和footer的大小位置等,UICollectionViewFlowLayout 继承自UICollectionViewLayout是苹果公司封装好的layout,能够实现简单的网格和线性布局,当cell的大小和间距同样时能够用UICollectionViewFlowLayout,若是要实现比较复杂的布局,就须要自定义了。

其次,要了解UICollectionViewLayoutAttributes 类的属性,如下是每个cell的属性,都是经过UICollectionViewLayoutAttributes属性体现出来的。

CGRect frame; // cell的大小已经x,y值

CGPoint center;//cell的中心点

CGSize size;// cell的size

CATransform3D transform3D;// cell的3D旋转

CGRect bounds NS_AVAILABLE_IOS(7_0);

CGAffineTransform transform NS_AVAILABLE_IOS(7_0); // cell 的旋转

CGFloat alpha;//alp值

NSInteger zIndex; // default is 0 //z轴

getter=isHidden) BOOL hidden; // As an optimization,
复制代码

还有,要理解UICollectionViewLayout的几个方法:

  1. prepareLayout :是专门用来准备布局的,在prepareLayout方法里面咱们能够事先就计算后面要用到的布局信息并存储起来,防止后面方法屡次计算,提升性能。例如,咱们能够在此方法就计算好每一个cell的属性、整个CollectionView的内容尺寸等等。此方法在布局以前会调用一次,以后只有在调用invalidateLayout、shouldInvalidateLayoutForBoundsChange:返回YES和UICollectionView刷新的时候才会调用。

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
复制代码

返回对应的indexPath的cell的attributes

-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
复制代码

返回对应的header和footer的attributes

- (CGSize)collectionViewContentSize
复制代码

; collectionView的size 这个size不是可视范围的size是整个collectionView的size

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 
复制代码

返回在rect范围内全部cell footer和head的attribute

了解以上的几点就能够开始计算了。计算的顺序是从上到下,即从区头到每一个区的cell再到区尾

设置一些数组用于存储计算好的值:以下

//存放attribute的数组

@property (nonatomic, strong) NSMutableArray *attrsArray;

//存放当前区中各个列的当前的高度

@property (nonatomic, strong) NSMutableArray *columnHeights;

//collectionView的Content的高度

@property (nonatomic, assign) CGFloat contentHeight;

//记录每一个区最高的

@property (nonatomic, assign) CGFloat lastContentHeight;

//每一个区的区头和上个区的区尾的距离

@property (nonatomic, assign) CGFloat spacingWithLastSection;
复制代码

首先是重写 prepareLayout方法,也是最重要的一步。在此方法中完成初始化。全部的计算都置为零。

第一步:经过

[self.collectionView numberOfSections]
复制代码

方法获取collectionView中一共有几个区。设置一个for循环。

第二步:经过

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
复制代码

获取每一个header的属性。计算完成以后把attributes添加到attrsArray 数组中 ,同时还有根据sectionInsets 等参数改变contentHeight 的高度

第三步: 设置区头完成以后,在循环中根据

[self.collectionView numberOfItemsInSection:i]
复制代码

获取相应的区有多少个cell

在这一步中计算是最麻烦的。能够分为以下步骤:

  1. 计算每个cell的frame。 根据此区中一共有几列和屏幕的宽度,以及每一个cell的之间的间距,计算出每一个cell的宽度,由于高度是外面传过来的,因此高度不须要计算 。那么还须要知道cell的x值和y值。

x值:首先取出当前区的中哪一列最低。

NSInteger tempMinColumn = 0; //默认第 0 列最小

CGFloat minColumnHeight = [self.columnHeights[0] doubleValue]; //
复制代码

取出最小的那一列的高度

for (NSInteger i = 0; i < self.columnCount; i ++) {

CGFloat columnH = [self.columnHeights[i] doubleValue];

if (minColumnHeight > columnH) {

minColumnHeight = columnH;

tempMinColumn = i;

} else {}

}
复制代码

tempMinColumn 就是最小的那一列

x值就能够根据sectionInsets , 每一个cell的左右间距,和cell的宽度算出

CGFloat cellX = self.sectionInsets.left + tempMinColumn * (cellWeight + self.interitemSpacing);
复制代码

y值:上面已经求出高度最小的那一列,以及最小的那一列的高度。

y值就 cellY = minColumnHeight

注意://若是cell的y值不等于上个区的最高的高度 即不是此区的第一列 要加上此区的每一个cell的上下间距

if (cellY != self.lastContentHeight) {

cellY += self.lineSpacing;

} else {}
复制代码

这样就能够知道了 cell的frame了, 即

attributes.frame = CGRectMake(cellX, cellY, cellWeight, cellHeight);
复制代码
  1. 要更新 contentHeight (当前collectionView的内容的高度) 和columnHeights(当区的每列的高度或者说每列的最后一个cell的y值 + height)

那么这样相应cell的值就计算完毕 ,在此函数返回值处添加到attrsArray 中去。

第四步:同header的计算方式同样 在

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
复制代码

计算footer的frame

一共多少个区 ,每一个区的header的frame是多少,每一个区中有多少个cell 每一个cell的frame是多少 ,每一个区的footer的frame是多少,以此循环计算出全部的attributes,在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 返回计算的attributes

  • [x] 注意 :在计算每一个attributes时 collectionView的内容的高度即contentHeight collectionView上个区的最高的那一列的高度即lastContentHeight 都在改变。

- (CGSize)collectionViewContentSize {

return CGSizeMake(self.collectionView.frame.size.width, self.contentHeight);

}
复制代码

中返回collectionView ContentSize 完成布局。

为了支持扩展性和易用性,我彻底模仿 UICollectionViewFlowLayout 的用法设置代理方法和属性。至于其使用方法,和UICollectionViewFlowLayout 同样的。代理方法和属性以下。

@property (nonatomic, weak) id delegate;

// 区的sectionInsets

@property (nonatomic,assign) UIEdgeInsets sectionInsets;

//每一个区的列数

@property (nonatomic,assign) NSInteger columnCount;

// 每一个cell的上下间距

@property (nonatomic,assign) CGFloat lineSpacing;

//每一个cell的左右间距

@property (nonatomic,assign) CGFloat interitemSpacing;

//header的size

@property (nonatomic,assign) CGSize headerReferenceSize;

// footer的size

@property (nonatomic,assign) CGSize footerReferenceSize;
复制代码

上述的这些参数 若是每一个区都同样,则能够在layout初始化的时候设置,如过每一个区的参数设置都不同,好比第一个区是两列,第二个区是一列,不用担忧,用代理。

代理方法支持分区设置这些参数。

@protocol JWCCustomLayoutDelegate

@required

// cell 高

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout heightForRowAtIndexPath:(NSIndexPath *)indexPath itemWidth:(CGFloat)itemWidth ;
复制代码
@optional

// headersize

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
复制代码

// footer 的 size

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
复制代码

// 每一个区的边距

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
复制代码

// 每一个区多少列

- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout columnNumberAtSection:(NSInteger )section;
复制代码

// 每一个区多少中行距

- (NSInteger)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout *)collectionViewLayout lineSpacingForSectionAtIndex:(NSInteger)section;
复制代码

// 每一个 item 之间的左右间距

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout interitemSpacingForSectionAtIndex:(NSInteger)section;
复制代码

// 本区区头和上个区区尾的间距

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(JWCCustomLayout*)collectionViewLayout spacingWithLastSectionForSectionAtIndex:(NSInteger)section;  (注意:在collectionViewFolwLayout中是没法设置当前的区头和上个区尾的间距的,为了弥补这一缺憾,特此添加这个方法)
复制代码

以上只是大体计算步骤,具体实现代码见Demo在这

转载请注明出处https://juejin.im/post/5a5274e46fb9a01c9f5b3eee 谢谢

看完若是对你有用 请点赞鼓励下中不中?🤣 🤣

效果图以下

第0区三列第一区四列

区头和区尾设置间距

单个区两列效果图
相关文章
相关标签/搜索