自定义UICollectionView布局

前言

相对于UITableView而言,UICollectionView具有更高的定制性和灵活性。它将其子视图的位置,大小和外观的控制权委托给一个单独的布局对象。通过提供这样一个自定义的布局对象,你几乎可以实现任何你能想想到的布局。布局继承自UICollectionViewLayout这几个抽象基类。IOS6中以UICollectionViewFlowLayout类的形式提出了一个具体的布局实现。

自定义布局

一般有两种类型的collection view布局:

1.独立于内容的布局计算。

每个cell的位置和外观不是基于其显示的内容,但是所有的cell的显示顺序是基于内容的顺序。可以把默认的flowlayout作为例子。每个cell都基于前一个cell放置(或者如果没有足够的空间,则从下一行开始)。布局对象不必访问实际数据来计算布局。

2.基于内容的布局计算。

布局对象不仅需要取出当前可见cell的数据,还需要从所有记录中取出一些决定当前哪些cell可见的数据。

如果有一个依赖内容的布局,那就是暗示你需要写自定义的布局类了,同时需要自定义一个类继承自UICollectionViewFlowLayout。重写子类的方法来重新布局。

步骤分解

我们今天要完成的例子的效果如下。是不是还比较炫酷。(哈哈,跟着本文敲到最后,你也可以的,相信我!)
自定义UICollectionView布局_第1张图片
自定义collectionView
其实UICollectionView布局非常简单。通过对程序打上断点,你会发现只需重写以下几个UICollectionViewLayout方法就能实现。
-(void)prepareLayout

首先做布局的准备工作,我们主要做两件事:1.用一个数组保存每列cell的origin.x。2.随机生成所有cell的高度,并用数组保存

-(CGSize)collectionViewContentSize

提供collectionView的滚动区域大小。第一次进来的时候是0。

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

为每一个cell加一个layout属性

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

通过给每个cell的frame重新赋值来改变cell的坐标

-(CGSize)collectionViewContentSize

最终根据cell的布局排列来确定collectionView的滚动区域大小

干完以上的活后,运行你的程序,当当当当。。。成功了。。。简单吧!下面我们来具体分解以下以上的几个方法。

详细分析

-(void)prepareLayout:

首先我们通过以下的方法获取collctionView有多少组,每组有多少列的数据

//初始化数据
    _numberOfSections = [self.collectionView numberOfSections];//多少组
    _numberOfCellsInSections = [self.collectionView numberOfItemsInSection:0];//每组多少个cell

再计算出每列cell的origin.x,并用数组保存起来

//计算出每个cell的宽度
    _cellWidth = (SCREEN_WIDTH - (_columnCount - 1)*_padding)/_columnCount;
//为每个cell计算x坐标
    _cellXArray = [NSMutableArray arrayWithCapacity:_columnCount];
    for (int i = 0; i<_columnCount; i++) {
        CGFloat tempX = i * (_cellWidth + _padding);
        [_cellXArray addObject:@(tempX)];
    }

最后随机生成所有cell的高度,保存到数组中

 //随机生成cell的高度
    _cellHeightArray = [NSMutableArray arrayWithCapacity:_numberOfCellsInSections];
    for (int i = 0; i<_numberOfCellsInSections; i++) {
        CGFloat cellHeight = arc4random() % (_cellMaxHeight - _cellMinHeight) + _cellMinHeight;
        [_cellHeightArray addObject:@(cellHeight)];
    }
-(CGSize)collectionViewContentSize

在刚进来的时候会走一次,为每个可见cell重新赋值frame之后又会走一次。maxCellYArrayWithArray:方法是用来求最大的cell的y坐标。以此来确定collection view 的滚动区域

  -(CGSize)collectionViewContentSize{
        CGFloat height = [self maxCellYArrayWithArray:_cellYArray];
        return CGSizeMake(SCREEN_WIDTH, height);
    }
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

为每一个cell加一个layout属性,返回一个数组。
initCellYArray方法是创建一个数组用来保存每列即将要排列的cell的origin.y

 -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    
    [self initCellYArray];
    
    NSMutableArray *array = [NSMutableArray array];
    
    for (int i = 0; i<_numberOfCellsInSections; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        
        UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
        
        [array addObject:attribute];
    }
        return array;
    }
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

通过给每个cell的frame重新赋值来改变cell的坐标。你需要布局多少个cell,那么这个方法就会走多少次。每次都为每个cell重新赋上属性。本文里面只包含了它的frame。你还可以添加其他的属性,建议点开UICollectionViewLayoutAttributes的源文件查看。

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    CGRect frame = CGRectZero;
    CGFloat cellHeight = [_cellHeightArray[indexPath.row] floatValue];//cell的高度
    
    NSInteger minYIndex = [self minCellYArrayWithArray:_cellYArray];//cellY最小值的索引
    CGFloat tempX = [_cellXArray[minYIndex] floatValue];//x坐标
    CGFloat tempY = [_cellYArray[minYIndex] floatValue];//y坐标
    
    frame = CGRectMake(tempX, tempY, _cellWidth, cellHeight);//通过哪列最新的Y最大坐标,判断哪列的cell最短,就在哪列加一个cell

    //修改列里面的Y坐标
    _cellYArray[minYIndex] = @(tempY + cellHeight + _padding);
    
    attribute.frame = frame;
    
    return attribute;
    }

如果到这里你没有弄错的话,我相信也不会弄错的吧,大家都很聪明的,哈哈。那么commond + R 一下,你就能得到本文一开篇就献上的效果图了。是不是很有成就感呢。
代码的完整版见Github demo

你可能感兴趣的:(自定义UICollectionView布局)