前言
最近看了www.raywenderlich.com的关于UICollectionView自定义布局的的教程,作一下笔记,方便以后查阅。UICollectionView自定义布局的基本概念可以查看喵神的WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts这篇文章。
基本原理
下面是轮盘旋转图解,黄色的区域是手机屏幕,蓝色的圆角矩形是UICollectionViewCells,cell以radius为半径,以手机屏幕外的一点为圆心旋转,每个cell之间的夹角为anglePerItem。
正如你看到的,不是所有的cell都在屏幕内,假设第0个cell的旋转角度是angle,那么第1个cell的旋转角度就是angle + anglePerItem,以此类推可以得到第i个cell的旋转角度:
CGFloat angleFori = angle + anglePerItem *i
复制代码下面是角度坐标是,0°代表的是屏幕中心,向左为负方向,向右为正方向,所以当cell的角度为0°时是垂直居中的。
自定义布局属性
因为系统的UICollectionViewLayoutAttributes没有angle和anchorPoint属性,所以我们继承UICollectionViewLayoutAttributes自定义布局属性。
@implementation WheelCollectionLayoutAttributes
- (instancetype)init{
self = [super init];
if (self) {
self.anchorPoint = CGPointMake(0.5, 0.5);
self.angle = 0;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone{
WheelCollectionLayoutAttributes *attribute = [super copyWithZone:zone];
attribute.anchorPoint = self.anchorPoint;
attribute.angle = self.angle;
return attribute;
}
@end
复制代码初始化时锚点anchorPoint是CGPointMake(0.5, 0.5),angle是0,并且重写- (id)copyWithZone:(NSZone *)zone方法,当对象被拷贝的时候,保证anchorPoint和angle属性被赋值。
**注意:**对于自定义的布局属性,UICollectionViewCell必须实现下面?这个方法,用来改变cell的锚点,改变锚点会引起layer的position的变化,关于anchorPoint和position的关系请看这篇文章。
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes{
[super applyLayoutAttributes:layoutAttributes];
//改变锚点(改变锚点会影响center的位置 参考http://www.jianshu.com/p/15f007f40209 )
WheelCollectionLayoutAttributes *attribute = (WheelCollectionLayoutAttributes*)layoutAttributes;
self.layer.anchorPoint = attribute.anchorPoint;
CGFloat centerY = (attribute.anchorPoint.y - 0.5)*CGRectGetHeight(self.bounds);
self.center = CGPointMake(self.center.x, centerY + self.center.y);
}
复制代码自定义布局
- 首先进行初始化: ?这个方法告诉
CollectionView布局属性使用自定义的WheelCollectionLayoutAttributes属性,而不是使用系统默认的UICollectionViewLayoutAttributes属性。
+ (Class)layoutAttributesClass{
return [WheelCollectionLayoutAttributes class];
}
复制代码- (instancetype)init{
self = [super init];
if (self) {
/*圆形半径*/
self.radius = 500.0;
/**itemCell的大小*/
self.itermSize = CGSizeMake(133, 173);
/*
每两个item之间的旋转角度,由*图一*可计算得到
self.anglePerItem可以是任意值
*/
self.anglePerItem = atan(self.itermSize.width / self.radius);
}
return self;
}
复制代码-
- (void)prepareLayout当collection view第一次显示在屏幕上时会被调用,这个方法初始化布局属性,并将布局属性保存下来。
- (void)prepareLayout{
[super prepareLayout];
[self.allAttributeArray removeAllObjects];
NSUInteger numberOfItem = [self.collectionView numberOfItemsInSection:0];
if (numberOfItem == 0) {
return;
}
/*获取总的旋转的角度*/
CGFloat angleAtExtreme = (numberOfItem - 1) * self.anglePerItem;
/*随着UICollectionView的移动,第0个cell初始时的角度*/
CGFloat angle = -1 *angleAtExtreme * self.collectionView.contentOffset.x / (self.collectionView.contentSize.width - CGRectGetWidth(self.collectionView.bounds));
/*当前的屏幕中心的的坐标*/
CGFloat centerX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.bounds)/2.0;
/*锚点的位置*/
CGFloat anchorPointY = (self.itermSize.height/2.0 + self.radius) / self.itermSize.height;
for (int i = 0; i < numberOfItem; i++) {
WheelCollectionLayoutAttributes *attribute = [WheelCollectionLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
attribute.anchorPoint = CGPointMake(0.5, anchorPointY);
attribute.size = self.itermSize;
attribute.center = CGPointMake(centerX, CGRectGetMidY(self.collectionView.bounds));
attribute.angle = angle + self.anglePerItem *i;
attribute.transform = CGAffineTransformMakeRotation(attribute.angle);
attribute.zIndex = (int)(-1) *i *1000;
[self.allAttributeArray addObject:attribute];
}
}
复制代码
由起点和终点的状态,当self.collectionView.contentOffset.x = 0时,第0个cell的旋转角度为0,当self.collectionView.contentOffset.x = Max时,第0个cell的旋转角度最大是(numberOfItem - 1) * self.anglePerItem,所以根据数学知识可以得到第0个cell的旋转角度和偏移量之间的关系 (向左为负值):
CGFloat angle = -1 *angleAtExtreme * self.collectionView.contentOffset.x / (self.collectionView.contentSize.width - CGRectGetWidth(self.collectionView.bounds));
复制代码所以第i个cell的旋转的角度为:
attribute.angle = angle + self.anglePerItem *i;
复制代码
由上图可以计算出cell的锚点,X轴方向的值不变,Y轴方向的值发生变化:
CGFloat anchorPointY = (self.itermSize.height/2.0 + self.radius) / self.itermSize.height;
CGFloat anchorPoint = CGPointMake(0.5, anchorPointY);
复制代码3.- (CGSize)collectionViewContentSize返回collectionView的内容大小
- (CGSize)collectionViewContentSize{
CGFloat numberOfIterm = [self.collectionView numberOfItemsInSection:0];
return CGSizeMake(numberOfIterm *self.itermSize.width, CGRectGetHeight(self.collectionView.bounds));
}
复制代码4.返回对应的布局属性
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.allAttributeArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
return self.allAttributeArray[indexPath.item];
}
复制代码- 当
collectionView滑动时,重新对collectionView进行布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
复制代码最后你可以到GitHub下载Demo。
















