今天研究了一下自定义UICollectionViewLayout。 看了看 官方文档 ,要自定义UICollectionViewLayout,需要创建一个UICollectionViewLayout的子类。同时,可以通过一下3个方法传递布局信息、contentSize、cells的信息等。
一、继承UICollectionViewLayout,重写以下方法
1.通过prepareLayout方法来计算预先计算需要提供的布局信息。
2.通过collectionViewContentSize方法来返回contentSize
3.通过layoutAttributesForElementsInRect: 方法来返回每个cell的信息
二、创建UICollectionViewLayoutAttributes,创建的方法有一下三种
1.layoutAttributesForCellWithIndexPath:
2.layoutAttributesForSupplementaryViewOfKind:withIndexPath:
3.layoutAttributesForDecorationViewOfKind:withIndexPath:
其中,layoutAttributesForCellWithIndexPath:方法创建cell的属性,layoutAttributesForSupplementaryViewOfKind:withIndexPath:创建补充视图的属性,如header、footer,layoutAttributesForDecorationViewOfKind:withIndexPath:创建修饰视图的属性
基础知识介绍完了,接下讲具体示例 创建一个UICollectionViewLayout的子类WKFlowLayout,
@interface WKFlowLayout : UICollectionViewLayout @property (nonatomic, strong) NSMutableDictionary *layoutInformation; @property (nonatomic) NSInteger maxNumCols; @end static NSUInteger CellWidth = 100; static CGFloat ContentHeight; @implementation WKFlowLayout { NSMutableArray *_yOffsets;//存储各列的当前offest }
接下来在prepareLayout预先计算布局信息
- (void)prepareLayout { _maxNumCols = 2;//设置为两列 _yOffsets = [NSMutableArray arrayWithCapacity:_maxNumCols]; for (int i = 0; i < _maxNumCols; i++) { [_yOffsets addObject:@0]; } //初始化cell的宽度 CellWidth = self.collectionView.bounds.size.width / _maxNumCols; //事先创建好UICollectionViewLayoutAttributes _layoutInformation = [NSMutableDictionary dictionary]; NSIndexPath *indexPath; NSInteger numSections = [self.collectionView numberOfSections]; for(NSInteger section = 0; section < numSections; section++){ NSInteger numItems = [self.collectionView numberOfItemsInSection:section]; for(NSInteger item = 0; item < numItems; item++){ indexPath = [NSIndexPath indexPathForItem:item inSection:section]; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; NSInteger col = indexPath.item % _maxNumCols; WKFlowLayoutDataSource *ds = self.collectionView.dataSource; NSNumber *height = ds.dataSource[indexPath.row]; attributes.frame = CGRectMake(col * CellWidth, [_yOffsets[col] floatValue], CellWidth, [height floatValue]); CGFloat yOffset; yOffset = [_yOffsets[col] floatValue] + [height floatValue]; NSLog(@"yOffset:%f col:%ld", yOffset, (long)col); _yOffsets[col] = @(yOffset); [_layoutInformation setObject:attributes forKey:indexPath]; //计算滚动高度 ContentHeight = MAX(ContentHeight, CGRectGetMaxY(attributes.frame)); } } }
剩下的代码
- (CGSize)collectionViewContentSize { CGFloat contentWidth = self.collectionView.bounds.size.width; CGSize contentSize = CGSizeMake(contentWidth, ContentHeight); return contentSize; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *myAttributes = [NSMutableArray arrayWithCapacity:self.layoutInformation.count]; for(NSString *key in self.layoutInformation.allKeys){ UICollectionViewLayoutAttributes *attributes = [self.layoutInformation objectForKey:key]; if(CGRectIntersectsRect(rect, attributes.frame)){ [myAttributes addObject:attributes]; } } return myAttributes; }
以上就是主要的实现代码了,需要注意的是,在prepareLayout中预先算出所有的布局信息适用于cell个数小于1000,超过之后在耗时就过长了,用户体验不好,同时需要在- (BOOL)shouldInvalidateLayoutForBoundsChange:方法中返回NO,此方法表示不需要再滚动过程中不停的调用prepareLayout
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return NO; }
示例代码 再附上官方的 circleLayout