最近看了www.raywenderlich.com的关于UICollectionView自定义布局的的教程,做一下笔记,方便之后查阅。UICollectionView自定义布局的基本概念能够查看喵神的WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts这篇文章。git
下面是轮盘旋转图解,黄色的区域是手机屏幕,蓝色的圆角矩形是UICollectionViewCells
,cell以radius
为半径,以手机屏幕外的一点为圆心旋转,每一个cell之间的夹角为anglePerItem
。github
正如你看到的,不是全部的cell都在屏幕内,假设第0个cell的旋转角度是angle,那么第1个cell的旋转角度就是angle + anglePerItem
,以此类推能够获得第i个cell的旋转角度:bash
CGFloat angleFori = angle + anglePerItem *i
复制代码
下面是角度坐标是,0°表明的是屏幕中心,向左为负方向,向右为正方向,因此当cell的角度为0°时是垂直居中的。app
由于系统的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
属性被赋值。ui
**注意:**对于自定义的布局属性,UICollectionViewCell
必须实现下面👇这个方法,用来改变cell的锚点,改变锚点会引发layer的position
的变化,关于anchorPoint
和position
的关系请看这篇文章。spa
- (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的旋转角度和偏移量之间的关系 (向左为负值):3d
CGFloat angle = -1 *angleAtExtreme * self.collectionView.contentOffset.x / (self.collectionView.contentSize.width - CGRectGetWidth(self.collectionView.bounds));
复制代码
因此第i个cell的旋转的角度为:code
attribute.angle = angle + self.anglePerItem *i;
复制代码
由上图能够计算出cell的锚点,X轴方向的值不变,Y轴方向的值发生变化:orm
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。