iOS流佈局UICollectionView系列六——將佈局從平面應用到空間

iOS流佈局UICollectionView系列六——將佈局從平面應用到空間

1、引言

        前面,咱們將佈局由線性的瀑布流佈局擴展到了圓環佈局,這使咱們使用UICollectionView的佈局思路大大邁進了一步,此次,咱們玩的更加炫一些,想辦法將佈局應用的空間,你是否還記得,在管理佈局的item的具體屬性的類UICollectionViewLayoutAttributrs類中,有transform3D這個屬性,經過這個屬性的設置,咱們真的能夠在空間的座標系中進行佈局設計。iOS系統的控件中,也並不是沒有這樣的先例,UIPickerView就是很好的一個實例,這篇博客,咱們就經過使用UICollectionView實現一個相似系統的UIPickerView的佈局視圖,來體會UICollectionView在3D控件佈局的魅力。系統的pickerView效果以下:數組

2、先來實現一個炫酷的滾輪空間佈局

        萬丈的高樓也是由一磚一瓦堆砌而成,在咱們徹底模擬系統pickerView前,咱們應該先將視圖的佈局擺放這一問題解決。咱們依然來建立一個類,繼承於UICollectionViewLayout:dom

@interface MyLayout : UICollectionViewLayout

@end

對於.m文件的內容,前幾篇博客中咱們都是在prepareLayout中進行佈局的靜態設置,那是由於咱們前幾篇博客中的佈局都是靜態的,佈局並不會隨着咱們的手勢操做而發生太大的變化,所以咱們所有在prepareLayout中一次配置完了。而咱們此次要討論的佈局則不一樣,pickerView會隨着咱們手指的拖動而進行滾動,所以UICollectionView中的每個item的佈局是在不斷變化的,因此此次,咱們採用動態配置的方式,在layoutAttributesForItemAtIndexPath方法中進行每一個item的佈局屬性設置。函數

        至於layoutAttributesForItemAtIndexPath方法,它也是UICollectionViewLayout類中的方法,用於咱們自定義時進行重寫,至於爲何動態佈局要在這裏面配置item的佈局屬性,後面咱們會了解到。佈局

        在編寫咱們的佈局類以前,先作好準備工做,在viewController中,實現以下代碼:spa

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 320, 400) collectionViewLayout:layout];
    collect.delegate=self;
    collect.dataSource=self;
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
    [self.view addSubview:collect];
}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 10;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
    UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 250, 80)];
    label.text = [NSString stringWithFormat:@"我是第%ld行",(long)indexPath.row];
    [cell.contentView addSubview:label];
    return cell;
}

上面我建立了10個Item,而且在每一個Item上添加了一個標籤,標寫是第幾行。設計

在咱們自定義的佈局類中重寫layoutAttributesForElementsInRect,在其中返回咱們的佈局數組:代理

-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSMutableArray * attributes = [[NSMutableArray alloc]init];
    //遍歷設置每一個item的佈局屬性
    for (int i=0; i<[self.collectionView numberOfItemsInSection:0]; i++) {
        [attributes addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]];
    }
    return attributes;
}

以後,在咱們佈局類中重寫layoutAttributesForItemAtIndexPath方法:code

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    //建立一個item佈局屬性類
    UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    //獲取item的個數
    int itemCounts = (int)[self.collectionView numberOfItemsInSection:0];
    //設置每一個item的大小爲260*100
    atti.size = CGSizeMake(260, 100);  
   /*
   後邊介紹的代碼添加在這裏
   
   */ 
    return atti;
}

上面的代碼中,咱們什麼都沒有作,下面咱們一步步來實現3D的滾輪效果。orm

首先,咱們先將全部的item的位置都設置爲collectionView的中心:對象

atti.center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2);

這時,若是咱們運行程序的話,全部item都將一層層貼在屏幕的中央,以下:

很醜對吧,以後咱們來設置每一個item的3D效果,在上面的佈局方法中添加以下代碼:

    //建立一個transform3D類
    //CATransform3D是一個相似矩陣的結構體
    //CATransform3DIdentity建立空得矩陣
    CATransform3D trans3D = CATransform3DIdentity;
    //這個值設置的是透視度,影響視覺離投影平面的距離
    trans3D.m34 = -1/900.0;
    //下面這些屬性 後面會具體介紹
    //這個是3D滾輪的半徑
    CGFloat radius = 50/tanf(M_PI*2/itemCounts/2);
    //計算每一個item應該旋轉的角度
    CGFloat angle = (float)(indexPath.row)/itemCounts*M_PI*2;
    //這個方法返回一個新的CATransform3D對象,在原來的基礎上進行旋轉效果的追加
    //第一個參數爲旋轉的弧度,後三個分別對應x,y,z軸,咱們須要以x軸進行旋轉
    trans3D = CATransform3DRotate(trans3D, angle, 1.0, 0, 0);
    //進行設置
    atti.transform3D = trans3D;

        對於上面的radius屬性,運用了一些簡單的幾何和三角函數的知識。若是咱們將系統的pickerView沿着y軸旋轉90°,你會發現側面的它是一個規則的正多邊形,這裏的radius就是這個多邊形中心到其邊的垂直距離,也是內切圓的半徑,全部的item拼成了一個正多邊形,示例以下:

經過簡單的數學知識,h/2弦對應的角的弧度爲2*pi/(邊數)/2,在根據三角函數相關知識可知,這個角的正切值爲h/2/radius,這就是咱們radius的由來。 

        對於angle屬性,它是每個item的x軸旋轉度數,若是咱們將全部item的中心都放在一點,經過旋轉讓它們散開以下圖所示:

每一個item旋轉的弧度就是其索引/(2*pi)。

經過上面的設置,咱們再運行代碼,效果以下:

仔細觀察咱們能夠發現,item以x中軸線進行了旋轉平均佈局,側面的效果就是咱們上面的簡筆畫那樣,下面要進行咱們的第三步了,將這個item,所有沿着其Z軸向前拉,就能夠成爲咱們滾輪的效果,示例圖以下:

咱們繼續在剛纔的代碼後面添加這行代碼:

 //這個方法也返回一個transform3D對象,追加平移效果,後面三個參數,對應平移的x,y,z軸,咱們沿z軸平移
 trans3D = CATransform3DTranslate(trans3D, 0, 0, radius);

再次運行,效果以下:

佈局的效果咱們已經完成了,離成功很近了對吧,只是如今的佈局是靜態的,咱們不能滑動這個滾輪,咱們還須要用動態滑動作一些處理。

3、讓滾輪滑動起來

            經過上面的努力,咱們已經靜態佈局出了一個相似pickerView的滾輪,如今咱們再來添加滑動滾動的效果

        首先,咱們須要給collectionView一個滑動的範圍,咱們以一屏collectionView的滑動距離來當作滾輪滾動一下的參照,咱們在佈局類中的以下方法中返回滑動區域:

-(CGSize)collectionViewContentSize{
    return CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height*[self.collectionView numberOfItemsInSection:0]);
}

這時咱們的collectionView已經能夠進行滑動,可是並非咱們想要的效果,滾輪並無滾動,而是隨着滑動出了屏幕,所以,咱們須要在滑動的時候不停的動態佈局,將滾輪始終固定在collectionView的中心,先須要在佈局類中實現以下方法:

//返回yes,則一有變化就會刷新佈局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
    
}

將上面的佈局的中心點設置加上一個動態的偏移量:

 atti.center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);

如今在運行,會發現滾輪會隨着滑動始終固定在中間,可是仍是不如人意,滾輪並無轉動起來,咱們還須要動態的設置每一個item的旋轉角度,這樣連續看起來,滾輪就轉了起來,在上面設置佈局的方法中,咱們在添加一些處理:

    //獲取當前的偏移量
    float offset = self.collectionView.contentOffset.y;
    //在角度設置上,添加一個偏移角度
    float angleOffset = offset/self.collectionView.frame.size.height;
    CGFloat angle = (float)(indexPath.row+angleOffset)/itemCounts*M_PI*2;

再看看效果,沒錯,就是這麼簡單,滾輪已經轉了起來。

4、讓其循環滾動的邏輯

        咱們再進一步,若是滾動能夠循環,這個控件將更加炫酷,添加這樣的邏輯也很簡單,經過監測scrollView的偏移量,咱們能夠對齊進行處理,由於collectionView繼承於scrollView,咱們能夠直接在ViewController中實現其代理方法,以下:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    //小於半屏 則放到最後一屏多半屏
    if (scrollView.contentOffset.y<200) {
        scrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y+10*400);
    //大於最後一屏多一屏 放回第一屏
    }else if(scrollView.contentOffset.y>11*400){
        scrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y-10*400);
    }
}

由於我們的環狀佈局,上面的邏輯恰好能夠無縫對接,可是會有新的問題,一開始運行,滾輪就是出如今最後一個item的位置,而不是第一個,而且有些相關的地方,咱們也須要一些適配:

在viewController中:

//一開始將collectionView的偏移量設置爲1屏的偏移量
collect.contentOffset = CGPointMake(0, 400);

在layout類中:

//將滾動範圍設置爲(item總數+2)*每屏高度 
-(CGSize)collectionViewContentSize{
    return CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height*([self.collectionView numberOfItemsInSection:0]+2));
}
//將計算的具體item角度向前遞推一個
CGFloat angle = (float)(indexPath.row+angleOffset-1)/itemCounts*M_PI*2;

OK,咱們終於大功告成了,能夠發現,實現這樣一個佈局效果炫酷的控件,代碼其實並無多少,相比,數學邏輯要比編寫代碼自己困難,這十分相似數學中的幾何問題,若是你弄清了邏輯,解決是分分鐘的事,咱們能夠經過這樣的一個思路,設計更多3D或者平面特效的佈局方案,抽獎的轉動圓盤,書本的翻頁,甚至立體的標籤雲,UICollectionView均可以實現,這篇博客中的代碼在下面的鏈接中,疏漏之處,歡迎指正!

http://pan.baidu.com/s/1jGCmbKM

專一技術,熱愛生活,交流技術,也作朋友。

——琿少 QQ羣:203317592

相關文章
相關標籤/搜索