UICollectionView詳解三:UICollectionViewLayout,
上一節中,我介紹了UICollectionViewFlowLayout的使用,它主要是使用在流式布局中的,但對於某些複雜的布局,UICollectionViewFlowLayout就不起作用了。這個時候,我們可以考慮使用UICollectionViewLayout。 UICollectionViewFlowLayout是繼承自UICollectionViewLayout,並且擁有自己的流式特性。對於一些複雜的效果,我們完全可以自訂UICollectionViewLayout來實現。
這一節,我就介紹使用UICollectionViewLayout來實現不同布局的來回切換。最終如下:
樣式1:
樣式2:
我們可以通過點擊螢幕的空白地區來回的切換上面兩種效果,並且切換過程中,動畫效果非常的流暢。
主介面的代碼和以前一樣,設定資料來源;初始化UICollectionView;註冊UICollectionViewCell;代碼如下:
@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>@property (nonatomic,strong) NSMutableArray *images;@property (nonatomic,weak) UICollectionView *collectionView;@endstatic NSString *const identifer = @"ImageCell";@implementation ViewController-(NSMutableArray *)images { if (!_images) { _images = [NSMutableArray array]; for (int i=1;i<=8;i++) { [_images addObject:[NSString stringWithFormat:@"%d.jpg",i]]; } } return _images;}- (void)viewDidLoad { [super viewDidLoad]; CGRect rect = CGRectMake(0, 150, self.view.frame.size.width,400); UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:rect collectionViewLayout:[[LFStackLayout alloc] init]]; collectionView.dataSource = self; collectionView.delegate = self; // 註冊collectionView(因為是從xib中載入cell的,所以registerNib) [collectionView registerNib:[UINib nibWithNibName:@"ImageCell" bundle:nil] forCellWithReuseIdentifier:identifer]; [self.view addSubview:collectionView]; self.collectionView = collectionView;}#pragma mark - 點擊螢幕空白處,切換配置模式- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if ([self.collectionView.collectionViewLayout isKindOfClass:[LFStackLayout class]]) { [self.collectionView setCollectionViewLayout:[[LFCircleLayout alloc] init] animated:YES]; } else { [self.collectionView setCollectionViewLayout:[[LFStackLayout alloc] init] animated:YES]; }}#pragma mark - delegate- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.images.count;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { ImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifer forIndexPath:indexPath]; cell.iconName = self.images[indexPath.item]; return cell;}- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // 1. 刪除模型資料 [self.images removeObjectAtIndex:indexPath.item]; // 2. 刪除UI元素 [collectionView deleteItemsAtIndexPaths:@[indexPath]];}
關鍵代碼就是這一段,用來實現“堆疊布局”和“圓形布局”的自由切換。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if ([self.collectionView.collectionViewLayout isKindOfClass:[LFStackLayout class]]) { [self.collectionView setCollectionViewLayout:[[LFCircleLayout alloc] init] animated:YES]; } else { [self.collectionView setCollectionViewLayout:[[LFStackLayout alloc] init] animated:YES]; }}
這裡,我就“圓形布局”進行說明,自訂LFCircleLayout, 並且繼承自UICollectionViewLayout
@interface LFCircleLayout : UICollectionViewLayout@end
UICollectionViewFlowLayout是可以直接擷取到所有Item的FlowLayout,然後對各自的layout進行調整;而UICollectionViewLayout完全需要自訂,來滿足自己的需求。主要是下面的代碼,重寫父類的 layoutAttributesForElementsInRect方法,返回的數組對象就是自訂layout的集合。當擷取到這個集合後,UICollectionView就會對每一個Item進行自動調節。
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { ...}
自訂LFCircleLayout中實現的layoutAttributesForElementsInRect方法如下:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *array = [NSMutableArray array]; NSInteger count = [self.collectionView numberOfItemsInSection:0]; for (int i=0; i<count; i++) { UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; [array addObject:attrs]; } return array;}
代碼中定義了一個方法:layoutAttributesForItemAtIndexPath, 專門用來設定每個Item的layout,完成“圓形布局”。代碼如下:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attrs.size = CGSizeMake(60, 60); // 第幾個Item NSInteger index = indexPath.item; // 半徑100 CGFloat radius = 100; // 圓心 CGFloat circleX = self.collectionView.frame.size.width * 0.5; CGFloat circleY = self.collectionView.frame.size.height * 0.5; NSInteger count = [self.collectionView numberOfItemsInSection:0]; CGFloat singleItemAngle = 360.0 / count; // 計算各個環繞的圖片center attrs.center = CGPointMake(circleX + radius * cosf(kCalcAngle(singleItemAngle * index)), circleY - radius * sinf(kCalcAngle(singleItemAngle * index))); return attrs;}
代碼中關於計算弧度的方法,我定義了一個宏來處理。
#define kCalcAngle(x) x * M_PI / 180.0
“堆疊布局”我就不做介紹了,基本類似,主要代碼如下:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attrs.size = CGSizeMake(150, 150); attrs.center = CGPointMake(self.collectionView.frame.size.width * 0.5, self.collectionView.frame.size.height * 0.5); NSInteger index = indexPath.item; CGFloat angles[] ={0,15,30,45,60}; NSInteger count = [self.collectionView numberOfItemsInSection:0]; if (index >= 5) { attrs.hidden = YES; } else { attrs.transform = CGAffineTransformMakeRotation(kCalcAngle(angles[index])); attrs.zIndex = count - index; } return attrs;}-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *array = [NSMutableArray array]; NSInteger count = [self.collectionView numberOfItemsInSection:0]; for (int i=0; i<count; i++) { UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; [array addObject:attrs]; } return array;}