自定义UICollectionView的布局

96
蓝苹果不是烂苹果
2016.08.31 23:30* 字数 848
不规则

关键方法

  • 1

      - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
    

    该方法用来返回rect范围内的 cell supplementary 以及 decoration的布局属性layoutAttributes(这里保存着她们的尺寸,位置,indexPath等等),如果你的布局都在一个屏幕内或者没有复杂的计算,这里可以返回全部的属性数组,如果涉及到复杂计算,应该进行判断,返回区域内的属性数组,有时候为了方便直接返回了全部的属性数组,不影响布局但可能会影响性能(如果你的item一屏幕显示不完,那么这个方法会调用多次,当所有的item都加载完毕后,在滑动collectionView时不会调用该方法的)。

  • 2

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

    该方法不是必须实现的,即便你实现了,我们对collectionView的任何操作,也不会导致系统主动调用该方法。该方法通常用来定制某个IndexPath的item的属性。当然我们也可以重写这个方法,将布局时相关的属性设置放在这里,在- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect 或者 - (void)prepareLayout 中 需要创建用来返回给系统的属性数组 主动调用这个方法,并添加带可变数组中去返回给系统。当然我们也可以在 - (void)prepareLayout中 通过[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:i inSection:0]] 获取 每个indexPath的attributes,在- (void)prepareLayout中设置所有item的属性。看需求以及个人喜欢。

  • 3

      - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;        
    

    用来刷新layout的,当我们返回yes的时候。如果我们的需求不需要实时的刷新layout,那么最好判断newBounds 和 我们的collectionView的bounds是否相同,如果不同就返回yes;(例如苹果官方的lineLayout,因为每次滑动都要放大item,所以这了就直接返回yes)。

  • 4

      - (void)prepareLayout;      
    

    第一次加载layout、刷新layout、以及- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;这个方法返回yes时,会调用。实现该方法后应该调用[super prepareLayout]保证初始化正确。该方法用来准备一些布局所需要的信息。该方法和init方法相似,但该方法可能会被调用多次,所以一些不固定的计算(比如该计算和collectionView的尺寸相关),最好放在这里,以保证collectionView发生变化时,自定义CollectionView能做出正确的反应。

举例

需要展示大小不相等的几张图片

效果图

说明:图片0比其他几张图片大,从而影响到布局,所以我们自定义一种布局@interface SixImageLayout : UICollectionViewLayout

两个关键方法
返回所有cell的布局

    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
self.cellCount = 6;   共6个cell
NSMutableArray *attributes = [NSMutableArray array];
for (NSInteger i = 0; i < self.cellCount; i ++) {
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
    //调用下面的方法返回
    UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
    [attributes addObject:attrs];
}
return attributes;
}

计算每个位置的布局

  • (UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath {
    UICollectionViewLayoutAttributes attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attributes.size = CGSizeMake(kCellSize, kCellSize);
    if (indexPath.row == 0) {
    attributes.size = CGSizeMake(kCellSize
    2+1, kCellSize
    2+1);
    attributes.center = CGPointMake((kCellSize
    2+1)/2, (kCellSize2+1)/2);
    }
    else if (indexPath.row == 1) {
    attributes.center = CGPointMake(kCellSize
    2+2+kCellSize/2, kCellSize/2);
    } else if(indexPath.row == 2) {
    attributes.center = CGPointMake(kCellSize2+2+kCellSize/2, kCellSize+kCellSize/2+1);
    }else if (indexPath.row == 3) {
    attributes.center = CGPointMake(kCellSize/2, kCellSize
    2+2+kCellSize/2);
    } else if(indexPath.row == 4) {
    attributes.center = CGPointMake(kCellSize+kCellSize/2+1, kCellSize2+2+kCellSize/2);
    } else {
    attributes.center = CGPointMake(kCellSize
    2+2+kCellSize/2, kCellSize*2+2+kCellSize/2);
    }
    return attributes;}

然后设置布局

    SixImageLayout *sixLayout = [[SixImageLayout alloc] init];
    self.collectionV = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 110, kScreenWidth, kScreenWidth) collectionViewLayout:sixLayout];

参考文章

完整demo请移步本人github

iOS