UICollectionView自定义流水布局
重点是重写系统的layout方法
1.自定义类继承UICollectionViewFlowLayout
2.UICollectionViewFlowLayout类会自动调用以下方法
-(void)prepareLayout;
将layout的布局准备工作都在这里面执行
-(CGSize)collectionViewContentSize;
返回的是collectionView的滑动范围,如果是动态加载的,就不要使用这个方法,可以直接设置每个Item的size,系统会通过size大小自动计算滑动范围
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
返回的是collectionView页面的布局,注意此处返回的是一个Attributes的数组
部分代码:
根据数据计算每个cell的大小
<span style="font-size:14px;">CGFloat columnHeight[self.column];
for (int i = 0; i < self.column; i++){
columnHeight[i] = self.sectionInset.top;
}
CGFloat cellW = (self.collectionView.frame.size.width - (self.column - 1) * self.minimumInteritemSpacing) / self.column;
for (int i = 0; i < self.pictures.count; i++){
Model *model = self.pictures[i];
CGFloat ratio = model.h.doubleValue / model.w.doubleValue;
CGFloat cellH = ratio * cellW;
UICollectionViewLayoutAttributes *layout = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGFloat cellX = i % self.column * (self.minimumInteritemSpacing + cellW);
CGFloat cellY = columnHeight[i % self.column];
layout.frame = CGRectMake(cellX, cellY, cellW, cellH );
//将每个cell的高度存起来
columnHeight[i % 3] += cellH + self.minimumLineSpacing;
[self.layouts addObject:layout];
}
//设置itemSize
CGFloat maxH = 0;
for (int i = 0; i < self.column; i++){
if (maxH < columnHeight[i]) {
maxH = columnHeight[i];
}
}
// NSLog(@"%f",maxH);
//计算行数
CGFloat row = 0;
if (self.pictures.count % self.column == 0) {
row = self.pictures.count / self.column;
}else {
row = self.pictures.count / self.column + 1;
}
CGFloat itemH = (maxH - (self.minimumLineSpacing ) * row) / row;
// CGFloat itemH = (maxH - self.sectionInset.top) / row;
//设置ItemSize系统会自动根据size大小计算应该滑动的范围
self.itemSize = CGSizeMake(cellW, itemH);</span>
计算的时候要注意行间距和第一行是否留有空隙来计算cell的高度,位置,否则会出现滑动到底的时候cell的底边和屏幕底边不会很好贴合
3.添加头部或底部的视图
collectionView和tableView一样,都可以添加头部和底部视图,两者原理一样,在这里添加一个底部的
首先实现方法:
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
头部与底部视图都是通过这个方法实现,可以用kind的值来区分添加的是哪个视图
头部:UICollectionElementKindSectionHeader
底部:UICollectionElementKindSectionFooter
然后在自定义的layout类中计算布局
<span style="font-size:14px;">//添加footer
UICollectionViewLayoutAttributes *footerAttr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
CGFloat footerH = 50;
footerAttr.frame = CGRectMake(0, maxH, self.collectionView.frame.size.width, footerH);
[self.layouts addObject:footerAttr];</span>
4.在动态加载cell的时候,可能会遇到类似这种错误
<span style="font-size:14px;">*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'layout attributes for supplementary item at index path (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}) changed from <UICollectionViewLayoutAttributes: 0x7fba41449b90> index path: (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}); element kind: (UICollectionElementKindSectionFooter); frame = (0 2982.19; 375 50); to <UICollectionViewLayoutAttributes: 0x7fba4164b760> index path: (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}); element kind: (UICollectionElementKindSectionFooter); frame = (0 5962.61; 375 50); without invalidating the layout'</span>
这个就是因为再一次加载layout时,layout的数据不对了,我们的数组里面一共存放了两次的布局,系统不能很好的匹配这些属性了
这个问题有两种解决办法:
(1)在每次计算属性时都创建一个局部的数组,将每次的数据放在这里,最后再将其赋给全局的布局属性数组即可
(2)创建两个全局属性的数组,每次都将其第一个数组,最后将第一个数组里面的数据放入第二个数组里,再将第一个数组数据清空(道理与第一个相同,不过赋值时记得用深拷贝,否则清空会两个数组都为空)
self.returnSize =self.layouts.mutableCopy;
[self.returnSizesetArray:self.layouts.copy];
[self.layoutsremoveAllObjects];
注:此方法都是在storyboard上做的
附项目源码:http://download.csdn.net/detail/aa603020460/9387678