参考github地址CHTCollectionViewWaterfallLayout
效果图:
1、关键思路:
1、多少列,多少section,每个section多少item
2、计算每个item的layoutAttribute。首先计算每个item的CGSize,根据数据源的下标确定数据放在哪个column上,从缓存中取出当前column的最大Y值,以及当前column处在的X值,由此就可算出当前item的frame,new一个新的layoutAttribute出来,设置上相应的frame即可。
3、展现的关键方法是:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSInteger i;
NSInteger begin = 0, end = self.unionRects.count;
NSMutableArray *attrs = [NSMutableArray array];
for (i = 0; i < self.unionRects.count; i++) {
if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) {
begin = i * unionSize;
break;
}
}
for (i = self.unionRects.count - 1; i >= 0; i--) {
if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) {
end = MIN((i + 1) * unionSize, self.allItemAttributes.count);
break;
}
}
for (i = begin; i < end; i++) {
UICollectionViewLayoutAttributes *attr = self.allItemAttributes[i];
if (CGRectIntersectsRect(rect, attr.frame)) {
[attrs addObject:attr];
}
}
return [NSArray arrayWithArray:attrs];
}
滑动的时候,会在准备展现某一块儿CGRect的时候,将其要展示的attribute给设置上。
2、CollectionViewLayout关键代码
- (void)prepareLayout {
[super prepareLayout];
[self.headersAttribute removeAllObjects];
[self.footersAttribute removeAllObjects];
[self.unionRects removeAllObjects];
[self.columnHeights removeAllObjects];
[self.allItemAttributes removeAllObjects];
[self.sectionItemAttributes removeAllObjects];
NSInteger numberOfSections = [self.collectionView numberOfSections];
if (numberOfSections == 0) {
return;
}
NSAssert([self.delegate conformsToProtocol:@protocol(CHTCollectionViewDelegateWaterfallLayout)], @"UICollectionView's delegate should conform to CHTCollectionViewDelegateWaterfallLayout protocol");
NSAssert(self.columnCount > 0 || [self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)], @"UICollectionViewWaterfallLayout's columnCount should be greater than 0, or delegate must implement columnCountForSection:");
// Initialize variables
NSInteger idx = 0;
for (NSInteger section = 0; section < numberOfSections; section++) {
NSInteger columnCount = [self columnCountForSection:section];
NSMutableArray *sectionColumnHeights = [NSMutableArray arrayWithCapacity:columnCount];
for (idx = 0; idx < columnCount; idx++) {
[sectionColumnHeights addObject:@(0)];
}
[self.columnHeights addObject:sectionColumnHeights];
}
// Create attributes
CGFloat top = 0;
UICollectionViewLayoutAttributes *attributes;
for (NSInteger section = 0; section < numberOfSections; ++section) {
/*
* 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset)
*/
CGFloat minimumInteritemSpacing;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) {
minimumInteritemSpacing = [self.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section];
} else {
minimumInteritemSpacing = self.minimumInteritemSpacing;
}
CGFloat columnSpacing = self.minimumColumnSpacing;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumColumnSpacingForSectionAtIndex:)]) {
columnSpacing = [self.delegate collectionView:self.collectionView layout:self minimumColumnSpacingForSectionAtIndex:section];
}
UIEdgeInsets sectionInset;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
} else {
sectionInset = self.sectionInset;
}
CGFloat width = self.collectionView.bounds.size.width - sectionInset.left - sectionInset.right;
NSInteger columnCount = [self columnCountForSection:section];
CGFloat itemWidth = CHTFloorCGFloat((width - (columnCount - 1) * columnSpacing) / columnCount);
/*
* 2. Section header
*/
CGFloat headerHeight;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForHeaderInSection:)]) {
headerHeight = [self.delegate collectionView:self.collectionView layout:self heightForHeaderInSection:section];
} else {
headerHeight = self.headerHeight;
}
UIEdgeInsets headerInset;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForHeaderInSection:)]) {
headerInset = [self.delegate collectionView:self.collectionView layout:self insetForHeaderInSection:section];
} else {
headerInset = self.headerInset;
}
top += headerInset.top;
if (headerHeight > 0) {
attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
attributes.frame = CGRectMake(headerInset.left,
top,
self.collectionView.bounds.size.width - (headerInset.left + headerInset.right),
headerHeight);
self.headersAttribute[@(section)] = attributes;
[self.allItemAttributes addObject:attributes];
top = CGRectGetMaxY(attributes.frame) + headerInset.bottom;
}
top += sectionInset.top;
for (idx = 0; idx < columnCount; idx++) {
self.columnHeights[section][idx] = @(top);
}
/*
* 3. Section items
*/
NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
NSMutableArray *itemAttributes = [NSMutableArray arrayWithCapacity:itemCount];
// Item will be put into shortest column.
for (idx = 0; idx < itemCount; idx++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section];
NSUInteger columnIndex = [self nextColumnIndexForItem:idx inSection:section];
CGFloat xOffset = sectionInset.left + (itemWidth + columnSpacing) * columnIndex;
CGFloat yOffset = [self.columnHeights[section][columnIndex] floatValue];
CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
CGFloat itemHeight = 0;
if (itemSize.height > 0 && itemSize.width > 0) {
itemHeight = CHTFloorCGFloat(itemSize.height * itemWidth / itemSize.width);
}
attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(xOffset, yOffset, itemWidth, itemHeight);
[itemAttributes addObject:attributes];
[self.allItemAttributes addObject:attributes];
self.columnHeights[section][columnIndex] = @(CGRectGetMaxY(attributes.frame) + minimumInteritemSpacing);
}
[self.sectionItemAttributes addObject:itemAttributes];
/*
* 4. Section footer
*/
CGFloat footerHeight;
NSUInteger columnIndex = [self longestColumnIndexInSection:section];
top = [self.columnHeights[section][columnIndex] floatValue] - minimumInteritemSpacing + sectionInset.bottom;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForFooterInSection:)]) {
footerHeight = [self.delegate collectionView:self.collectionView layout:self heightForFooterInSection:section];
} else {
footerHeight = self.footerHeight;
}
UIEdgeInsets footerInset;
if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForFooterInSection:)]) {
footerInset = [self.delegate collectionView:self.collectionView layout:self insetForFooterInSection:section];
} else {
footerInset = self.footerInset;
}
top += footerInset.top;
if (footerHeight > 0) {
attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
attributes.frame = CGRectMake(footerInset.left,
top,
self.collectionView.bounds.size.width - (footerInset.left + footerInset.right),
footerHeight);
self.footersAttribute[@(section)] = attributes;
[self.allItemAttributes addObject:attributes];
top = CGRectGetMaxY(attributes.frame) + footerInset.bottom;
}
for (idx = 0; idx < columnCount; idx++) {
self.columnHeights[section][idx] = @(top);
}
} // end of for (NSInteger section = 0; section < numberOfSections; ++section)
// Build union rects
idx = 0;
NSInteger itemCounts = [self.allItemAttributes count];
while (idx < itemCounts) {
CGRect unionRect = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame;
NSInteger rectEndIndex = MIN(idx + unionSize, itemCounts);
for (NSInteger i = idx + 1; i < rectEndIndex; i++) {
unionRect = CGRectUnion(unionRect, ((UICollectionViewLayoutAttributes *)self.allItemAttributes[i]).frame);
}
idx = rectEndIndex;
[self.unionRects addObject:[NSValue valueWithCGRect:unionRect]];
}
}