iOS开发进阶之旅-自定义UICollectionViewFlowlayout

来源:互联网 发布:js 获取端口号 编辑:程序博客网 时间:2024/06/08 10:29
无疑,iOS6引入的UICollectionView是UIKit视图类中的新星。UICollectionView在各个方面都有一定的UITableView的影子。而,相比于UITableView更强的定制性,更大的灵活性是UICollectionView完全超越UITableView的一点。

UICollectionView和UITableView都是dataSource和delegate驱动的,而相比于dataSource和delegate,UICollectionView之所以能比UITableView实现更强的定制性是因为UICollectionViewLayout的存在。UICollectionViewLayout是一个自定义布局对象,通过UICollectionViewLayout对象我们几乎可以实现任何我们想要的布局。

相信所有和我一样,刚接触UICollectionView的时候所接触到的布局都是系统提供的UICollectionViewFlowLayout,系统提供UICollectionViewFlowLayout也的确能满足我们一部分的需求。但是,UICollectionView的灵活性却被大大的桎梏住了。对自定义布局的研究也是出于需求的驱动,由于公司是做电商生意的,一些衣服的尺码表,每个尺码并不是等长的,系统提供的UICollectionViewFlowLayout显然是无法满足的,这就需要自己去定制一个UICollectionViewFlowLayout。

为了实现自定义UICollectionViewFlowLayout的常规做法是继承UICollectionViewFlowLayout类,然后重载下列方法:

  • -(CGSize)collectionViewContentSize

    • 返回collectionView的内容的尺寸
  • -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

    • 返回rect中的所有的元素的布局属性
    • 返回的是包含UICollectionViewLayoutAttributes的NSArray
    • UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:

      • layoutAttributesForCellWithIndexPath:
      • layoutAttributesForSupplementaryViewOfKind:withIndexPath:
      • layoutAttributesForDecorationViewOfKind:withIndexPath:
  • -(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath

    • 返回对应于indexPath的位置的cell的布局属性
  • -(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath

    • 返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载
  • -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath

    • 返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
  • -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

    • 当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。

另外需要了解的是,在初始化一个UICollectionViewLayout实例后,会有一系列准备方法被自动调用,以保证layout实例的正确。

首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。

之后,-(CGSize) collectionViewContentSize将被调用,以确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView的本质是一个scrollView,因此需要这个尺寸来配置滚动行为。

接下来-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被调用,这个没什么值得多说的。初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。

另外,在需要更新layout时,需要给当前layout发送 -invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始。


一个不同cell间,横向间隙一致,宽度不同的自定义UICollectionViewFlowLayout demo


#import "EqualSpaceFlowLayout.h"

const staticint bottomInset = 10;

@interface EqualSpaceFlowLayout()

@property (nonatomic,strong) NSMutableArray *itemAttributes;

@end


@implementation EqualSpaceFlowLayout

- (id)init

{

    if (self = [superinit]) {

        self.scrollDirection =UICollectionViewScrollDirectionVertical;

        self.minimumInteritemSpacing =4;

        self.minimumLineSpacing =0;

        self.sectionInset =UIEdgeInsetsMake(bottomInset,15, bottomInset, 15);//UIEdgeInsetsMake(10, 10, 10, 10);

    }

    returnself;

}


#pragma mark - Methods to Override


- (void)prepareLayout

{

    [superprepareLayout];

    

    NSInteger itemCount = [[selfcollectionView] numberOfItemsInSection:0];

    self.itemAttributes = [NSMutableArrayarrayWithCapacity:itemCount];

    

    CGFloat xOffset =self.sectionInset.left;

    CGFloat yOffset =self.sectionInset.top;

    CGFloat xNextOffset =self.sectionInset.left;

    for (NSInteger idx =0; idx < itemCount; idx++) {

        NSIndexPath *indexPath = [NSIndexPathindexPathForItem:idx inSection:0];

        CGSize itemSize = [self.delegatecollectionView:self.collectionViewlayout:selfsizeForItemAtIndexPath:indexPath];

        

        xNextOffset+=(self.minimumInteritemSpacing + itemSize.width);


        if (xNextOffset > [selfcollectionView].bounds.size.width - self.sectionInset.right) {

            xOffset = self.sectionInset.left;

            xNextOffset = (self.sectionInset.left +self.minimumInteritemSpacing + itemSize.width);

            yOffset += (itemSize.height +self.minimumLineSpacing);

        }

        else

        {

            xOffset = xNextOffset - (self.minimumInteritemSpacing + itemSize.width);

        }

        

        UICollectionViewLayoutAttributes *layoutAttributes =

        [UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:indexPath];

        

        layoutAttributes.frame =CGRectMake(xOffset, yOffset, itemSize.width, itemSize.height);

        [_itemAttributesaddObject:layoutAttributes];

    }

}


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

{

    return (self.itemAttributes)[indexPath.item];

}


- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

{

    return [self.itemAttributesfilteredArrayUsingPredicate:[NSPredicatepredicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject,NSDictionary *bindings) {

        returnCGRectIntersectsRect(rect, [evaluatedObjectframe]);

    }]];

}


- (CGSize)collectionViewContentSize{

    //获取最后一个cellframe拿到最Y加上bottomInset就是collection

    if (self.itemAttributes &&self.itemAttributes.count >0) {

        UICollectionViewLayoutAttributes *layoutAttributes = WKC_ObjectAtIndex(self.itemAttributes,self.itemAttributes.count -1);

        CGRect lastFrame = layoutAttributes.frame;

        returnCGSizeMake([selfcollectionView].bounds.size.width,CGRectGetMaxY(lastFrame)+bottomInset);

    }else{

        [selfprepareLayout];

        UICollectionViewLayoutAttributes *layoutAttributes = WKC_ObjectAtIndex(self.itemAttributes,self.itemAttributes.count -1);

        CGRect lastFrame = layoutAttributes.frame;

        returnCGSizeMake([selfcollectionView].bounds.size.width,CGRectGetMaxY(lastFrame)+bottomInset);

    }

}


- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

{

    returnNO;

}

@end



1 0
原创粉丝点击