前言
最近看了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。