同样使用 UICollectionView 来实现,自定义一个 UICollectionViewLayout 即可。UICollectionView 可以看作是具备复用控件的 ScrollView。自定义则是通过某些算法得到每一个控件在 Content 上的位置,并设置它具备的形态。该 UICollectionViewLayout 中获取并返回一个自定义的 UICollectionViewLayoutAttributes。
当UICollectionView在获取布局时将针对每一个indexPath的部件(包括cell,追加视图和装饰视图),向其上的UICollectionViewLayout实例询问该部件的布局信息(在这个层面上说的话,实现一个UICollectionViewLayout的时候,其实很像是zap一个delegate,之后的例子中会很明显地看出),这个布局信息,就以UICollectionViewLayoutAttributes的实例的方式给出。
简单地说就是 CollectionView 找 UICollectionViewLayout 要布局信息。UICollectionViewLayout 就给出 UICollectionViewLayoutAttributes 的实例。UICollectionViewLayout 在这过程中像提款机,UICollectionViewLayoutAttributes就像提款机中的钱。UICollectionView 需要钱,按提款机就行。
继承UICollectionViewLayout类,然后重载下列方法:
- (void)prepareLayout;
`首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。`- (CGSize)collectionViewContentSize;
返回 UICollectionView 的 contentSize
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
返回包括 cell 的 Attribute,SupplementaryView 和 DecorationView 的 Attribute 的数组。每个 Attribute 通过 representedElementCategory 属性来在数组中区分该 Attribute 是属于Cell 抑或是 supplementary view 或 decoration view。
可以重载三种对应方法返回 Attribute
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
通过对应方法返回对应 Attribute
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
(instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath*)indexPath;
下面是一种实现
#pragma mark -
//
// JDCollectionViewLayout.m
// JasonCaoDemoProject
//
// Created by 曹 景成 on 15/6/4.
// Copyright (c) 2015年 JasonCao. All rights reserved.
//
@interface JDCollectionViewLayout : UICollectionViewLayout
@property (nonatomic, assign) NSInteger cellCount;
@property (nonatomic) CGSize headerReferenceSize;
@end
@implementation JDCollectionViewLayout
- (void)prepareLayout {
[super prepareLayout];
_cellCount = [[self collectionView] numberOfItemsInSection:0];
}
- (CGSize)collectionViewContentSize {
NSInteger height = (_cellCount/7 +1)*262 + self.headerReferenceSize.height;
return CGSizeMake(CGRectGetWidth(self.collectionView.frame), height);
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
//每一小组起点(x, y)
NSInteger y = (path.row/7)*262 + self.headerReferenceSize.height;
NSInteger x = 8;
//每个cell在每个小组当中的位置
NSInteger indexForSix = (path.row+1)%7;
if(indexForSix == 0 || indexForSix == 6){
//小组当中下方两个中等cell
y += 254*0.4+14;
attributes.size = CGSizeMake((IOS_SCREEN_WIDTH-24)/2, 254*0.6);
if(indexForSix == 0) x = IOS_SCREEN_WIDTH/2+4;
}else if (indexForSix == 1){
//每一小组第一个cell
y += 8; //距离原点18点
attributes.size = CGSizeMake((IOS_SCREEN_WIDTH-24)/2, 254*0.4);
}else {
attributes.size = CGSizeMake((((IOS_SCREEN_WIDTH-24)/2)-8)/2, (254*0.4-8)/2);
if(indexForSix == 2){
y += 8;
x = IOS_SCREEN_WIDTH/2+4;
}
if(indexForSix == 3){
y += 8;
x = IOS_SCREEN_WIDTH/2+(((IOS_SCREEN_WIDTH-24)/2)-8)/2+12;
}
if(indexForSix == 4){
y += (254*0.4-8)/2+16;
x = IOS_SCREEN_WIDTH/2+4;
}
if(indexForSix == 5){
y += (254*0.4-8)/2+16;
x = IOS_SCREEN_WIDTH/2+(((IOS_SCREEN_WIDTH-24)/2)-8)/2+12;
}
}
attributes.center = CGPointMake(x+attributes.size.width/2, y+attributes.size.height/2);
return attributes;
}
//使用 Reveal 查看 headerView 被加了8个,只有一个 View 包含 SaKuraScrollView
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *headerAttributes = [UICollectionViewLayoutAttributes
layoutAttributesForSupplementaryViewOfKind:elementKind
withIndexPath:indexPath];
headerAttributes.size = self.headerReferenceSize;
headerAttributes.center = CGPointMake(IOS_SCREEN_WIDTH/2, headerAttributes.size.height/2);
NSLog(@"header---%lu",(unsigned long)headerAttributes.representedElementCategory);
return headerAttributes;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray* attributes = [NSMutableArray array];
for (NSInteger i=0 ; i < self.cellCount; i++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[attributes addObject:[self layoutAttributesForSupplementaryViewOfKind:@"header" atIndexPath:indexPath]];
return attributes;
}
@end
使用如下
- (void)initContentCollectionView {
JDCollectionViewLayout *layout = [[JDCollectionViewLayout alloc] init];
layout.headerReferenceSize = CGSizeMake(IOS_SCREEN_WIDTH, 200);
self.contentCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0 ,64.f , IOS_SCREEN_WIDTH, IOS_SCREEN_CONTENT_HEIGHT - BOTTOM_MENU_BAR_HEIGHT) collectionViewLayout:layout];
self.contentCollectionView.backgroundColor = LIGHT_GRAY;
self.contentCollectionView.delegate = self;
self.contentCollectionView.dataSource = self;
self.contentCollectionView.showsVerticalScrollIndicator = NO;
self.contentCollectionView.alwaysBounceVertical = YES;
//collectionView(上、左、下、右)
self.contentCollectionView.contentInset = COLLECTIONCONTENTINSET;
[self.contentCollectionView registerClass:[ViewHomeMenuCollectionCell class] forCellWithReuseIdentifier:@"cell"];
[self.contentCollectionView registerClass:[ViewHomeMenuCollectionCellMedium class] forCellWithReuseIdentifier:@"mediumCell"];
[self.contentCollectionView registerClass:[ViewHomeMenuCollectionCellSmall class] forCellWithReuseIdentifier:@"smallCell"];
[self.contentCollectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headAdView"];
[self addSubview:self.contentCollectionView];
}
另外还有类似的第三方库
RAMCollectionViewFlemishBondLayout
对 RAMCollectionViewFlemishBondLayout 库其中一段计算 Cell 的原点Y值代码注释记录
//获取每一个cell的原点Y值
- (CGFloat)getYAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger currentGroup = [self currentGroupAtIndexPath:indexPath];
CGFloat yValue = 0.0f;
NSIndexPath *indexPathFirstElementCurrentSection = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
if ([self isHighLightedElementAtIndexPath:indexPath]) {
//判断是否是 highLight ,计算得到 y 值
yValue = (currentGroup - 1) * self.highlightedCellHeight + [self heightHeaderAtIndexPath:indexPathFirstElementCurrentSection];
} else {
//这里需要加判断是否为下方两个中等cell?
//不是 highLight
NSInteger position;
//numberOfElements 为每一个小组的数量
//这里的思路是仅仅用算法计算每一个Cell的frame,按规律放到CollecitonView的Content上
//使用CollectionView而非ScrollView为了更好地实现复用
//
if (indexPath.row <= self.numberOfElements) {
//这里得到第一小组的小cell的位置position
//从 position = (indexPath.row - 1); 修改为
position = (indexPath.row - 1)%2;
//还需要算出positionX的位置
} else {
//第二小组及以后
NSInteger maxElement = self.numberOfElements * currentGroup;
//(maxElement - self.numberOfElements)可以算得每一个小组highLight的下标
position = (indexPath.row - 1) - (maxElement - self.numberOfElements);
}
if (position == 6 && position == 7) {
// yValue = ((currentGroup - 1) * self.highlightedCellHeight) +
} else {
yValue = ((currentGroup - 1) * self.highlightedCellHeight) + (self.cellHeight * position) + [self heightHeaderAtIndexPath:indexPathFirstElementCurrentSection];
}
}
if (indexPath.section > 0) {
yValue += (self.highlightedCellHeight * indexPath.section * [self totalGroupsAtIndexPath:indexPath]) + [self headerAndFooterHeightsPreviouslyAtIndexPath:indexPath];
}
return yValue;
}
参考:
UICollectionViewLayoutAttributes Class Reference
UICollectionView之自定义布局
iOS6新特征:UICollectionView介绍