iOS流布局UICollectionView系列一——初識(shí)與簡(jiǎn)單使用UICollectionView
????????UICollectionView是iOS6之后引入的一個(gè)新的UI控件,它和UITableView有著諸多的相似之處,其中許多代理方法都十分類似。簡(jiǎn)單來說,UICollectionView是比UITbleView更加強(qiáng)大的一個(gè)UI控件,有如下幾個(gè)方面:
1、支持水平和垂直兩種方向的布局
2、通過layout配置方式進(jìn)行布局
3、類似于TableView中的cell特性外,CollectionView中的Item大小和位置可以自由定義
4、通過layout布局回調(diào)的代理方法,可以動(dòng)態(tài)的定制每個(gè)item的大小和collection的大體布局屬性
5、更加強(qiáng)大一點(diǎn),完全自定義一套layout布局方案,可以實(shí)現(xiàn)意想不到的效果
這篇博客,我們主要討論CollectionView使用原生layout的方法和相關(guān)屬性,其他特點(diǎn)和更強(qiáng)的制定化,會(huì)在后面的博客中介紹
二、先來實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的九宮格類布局
????????在了解UICollectionView的更多屬性前,我們先來使用其進(jìn)行一個(gè)最簡(jiǎn)單的流布局試試看,在controller的viewDidLoad中添加如下代碼:
//創(chuàng)建一個(gè)layout布局類
UICollectionViewFlowLayout*?layout?=?[[UICollectionViewFlowLayoutalloc]init];
//設(shè)置布局方向?yàn)榇怪绷鞑季?/p>
layout.scrollDirection?=UICollectionViewScrollDirectionVertical;
//設(shè)置每個(gè)item的大小為100*100
layout.itemSize?=CGSizeMake(100,100);
//創(chuàng)建collectionView?通過一個(gè)布局策略layout來創(chuàng)建
UICollectionView*?collect?=?[[UICollectionViewalloc]initWithFrame:self.view.frame?collectionViewLayout:layout];
//代理設(shè)置
collect.delegate=self;
collect.dataSource=self;
//注冊(cè)item類型?這里使用系統(tǒng)的類型
[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];
[self.view?addSubview:collect];
這里有一點(diǎn)需要注意,collectionView在完成代理回調(diào)前,必須注冊(cè)一個(gè)cell,類似如下:
[collect?registerClass:[UICollectionViewCell?class]forCellWithReuseIdentifier:@"cellid"];
這和tableView有些類似,又有些不同,因?yàn)閠ableView除了注冊(cè)cell的方法外,還可以通過臨時(shí)創(chuàng)建來做:
//tableView在從復(fù)用池中取cell的時(shí)候,有如下兩種方法
//使用這種方式如果復(fù)用池中無,是可以返回nil的,我們?cè)谂R時(shí)創(chuàng)建即可
-?(nullable__kindofUITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier;
//6.0后使用如下的方法直接從注冊(cè)的cell類獲取創(chuàng)建,如果沒有注冊(cè)?會(huì)崩潰
-?(__kindofUITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier?forIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(6_0);
我們可以分析:因?yàn)閁ICollectionView是iOS6.0之前的新類,因此這里統(tǒng)一了從復(fù)用池中獲取cell的方法,沒有再提供可以返回nil的方式,并且在UICollectionView的回調(diào)代理中,只能使用從復(fù)用池中獲取cell的方式進(jìn)行cell的返回,其他方式會(huì)崩潰,例如:
//這是正確的方法
-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
returncell;
}
//這樣做會(huì)崩潰
-(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];
UICollectionViewCell*?cell?=?[[UICollectionViewCellalloc]init];
returncell;
}
上面錯(cuò)誤的方式會(huì)崩潰,信息如下,讓我們使用從復(fù)用池中取cell的方式:
上面的設(shè)置完成后,我們來實(shí)現(xiàn)如下幾個(gè)代理方法:
這里與TableView的回調(diào)方式十分類似
//返回分區(qū)個(gè)數(shù)
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView?*)collectionView{
return1;
}
//返回每個(gè)分區(qū)的item個(gè)數(shù)
-(NSInteger)collectionView:(UICollectionView?*)collectionViewnumberOfItemsInSection:(NSInteger)section{
return10;
}
//返回每個(gè)item
-(UICollectionViewCell?*)collectionView:(UICollectionView?*)collectionViewcellForItemAtIndexPath:(NSIndexPath?*)indexPath{
UICollectionViewCell?*?cell??=?[collectionViewdequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0?green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
returncell;
}
效果如下:
同樣,如果內(nèi)容的大小超出一屏,和tableView類似是可以進(jìn)行視圖滑動(dòng)的。
還有一點(diǎn)細(xì)節(jié),我們?cè)谏厦嬖O(shè)置布局方式的時(shí)候設(shè)置了垂直布局:
layout.scrollDirection?=UICollectionViewScrollDirectionVertical;
//這個(gè)是水平布局
//layout.scrollDirection?=?UICollectionViewScrollDirectionHorizontal;
這樣系統(tǒng)會(huì)在一行充滿后進(jìn)行第二行的排列,如果設(shè)置為水平布局,則會(huì)在一列充滿后,進(jìn)行第二列的布局,這種方式也被稱為流式布局
//通過一個(gè)布局策略初識(shí)化CollectionView
-?(instancetype)initWithFrame:(CGRect)frame?collectionViewLayout:(UICollectionViewLayout*)layout;
//獲取和設(shè)置collection的layout
@property(nonatomic,strong)UICollectionViewLayout*collectionViewLayout;
//數(shù)據(jù)源和代理
@property(nonatomic,weak,nullable)id?delegate;
@property(nonatomic,weak,nullable)id?dataSource;
//從一個(gè)class或者xib文件進(jìn)行cell(item)的注冊(cè)
-?(void)registerClass:(nullableClass)cellClass?forCellWithReuseIdentifier:(NSString*)identifier;
-?(void)registerNib:(nullableUINib*)nib?forCellWithReuseIdentifier:(NSString*)identifier;
//下面兩個(gè)方法與上面相似,這里注冊(cè)的是頭視圖或者尾視圖的類
//其中第二個(gè)參數(shù)是設(shè)置?頭視圖或者尾視圖?系統(tǒng)為我們定義好了這兩個(gè)字符串
//UIKIT_EXTERN?NSString?*const?UICollectionElementKindSectionHeader?NS_AVAILABLE_IOS(6_0);
//UIKIT_EXTERN?NSString?*const?UICollectionElementKindSectionFooter?NS_AVAILABLE_IOS(6_0);
-?(void)registerClass:(nullableClass)viewClass?forSupplementaryViewOfKind:(NSString*)elementKind?withReuseIdentifier:(NSString*)identifier;
-?(void)registerNib:(nullableUINib*)nib?forSupplementaryViewOfKind:(NSString*)kind?withReuseIdentifier:(NSString*)identifier;
//這兩個(gè)方法是從復(fù)用池中取出cell或者頭尾視圖
-?(__kindofUICollectionViewCell*)dequeueReusableCellWithReuseIdentifier:(NSString*)identifier?forIndexPath:(NSIndexPath*)indexPath;
-?(__kindofUICollectionReusableView*)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind?withReuseIdentifier:(NSString*)identifier?forIndexPath:(NSIndexPath*)indexPath;
//設(shè)置是否允許選中?默認(rèn)yes
@property(nonatomic)BOOLallowsSelection;
//設(shè)置是否允許多選?默認(rèn)no
@property(nonatomic)BOOLallowsMultipleSelection;
//獲取所有選中的item的位置信息
-?(nullableNSArray?*)indexPathsForSelectedItems;
//設(shè)置選中某一item,并使視圖滑動(dòng)到相應(yīng)位置,scrollPosition是滑動(dòng)位置的相關(guān)參數(shù),如下:
/*
typedef?NS_OPTIONS(NSUInteger,?UICollectionViewScrollPosition)?{
????//無
????UICollectionViewScrollPositionNone?????????????????=?0,
????//垂直布局時(shí)使用的?對(duì)應(yīng)上中下
????UICollectionViewScrollPositionTop??????????????????=?1?<<?0,
????UICollectionViewScrollPositionCenteredVertically???=?1?<<?1,
????UICollectionViewScrollPositionBottom???????????????=?1?<<?2,
????//水平布局時(shí)使用的??對(duì)應(yīng)左中右
????UICollectionViewScrollPositionLeft?????????????????=?1?<<?3,
????UICollectionViewScrollPositionCenteredHorizontally?=?1?<<?4,
????UICollectionViewScrollPositionRight????????????????=?1?<<?5
};
*/
-?(void)selectItemAtIndexPath:(nullableNSIndexPath*)indexPath?animated:(BOOL)animated?scrollPosition:(UICollectionViewScrollPosition)scrollPosition;
//將某一item取消選中
-?(void)deselectItemAtIndexPath:(NSIndexPath*)indexPath?animated:(BOOL)animated;
//重新加載數(shù)據(jù)
-?(void)reloadData;
//下面這兩個(gè)方法,可以重新設(shè)置collection的布局,后面的方法多了一個(gè)布局完成后的回調(diào),iOS7后可以用
//使用這兩個(gè)方法可以產(chǎn)生非常炫酷的動(dòng)畫效果
-?(void)setCollectionViewLayout:(UICollectionViewLayout*)layout?animated:(BOOL)animated;
-?(void)setCollectionViewLayout:(UICollectionViewLayout*)layout?animated:(BOOL)animated?completion:(void(^?__nullable)(BOOLfinished))completionNS_AVAILABLE_IOS(7_0);
//下面這些方法更加強(qiáng)大,我們可以對(duì)布局更改后的動(dòng)畫進(jìn)行設(shè)置
//這個(gè)方法傳入一個(gè)布局策略layout,系統(tǒng)會(huì)開始進(jìn)行布局渲染,返回一個(gè)UICollectionViewTransitionLayout對(duì)象
//這個(gè)UICollectionViewTransitionLayout對(duì)象管理動(dòng)畫的相關(guān)屬性,我們可以進(jìn)行設(shè)置
-?(UICollectionViewTransitionLayout*)startInteractiveTransitionToCollectionViewLayout:(UICollectionViewLayout*)layout?completion:(nullableUICollectionViewLayoutInteractiveTransitionCompletion)completionNS_AVAILABLE_IOS(7_0);
//準(zhǔn)備好動(dòng)畫設(shè)置后,我們需要調(diào)用下面的方法進(jìn)行布局動(dòng)畫的展示,之后會(huì)調(diào)用上面方法的block回調(diào)
-?(void)finishInteractiveTransitionNS_AVAILABLE_IOS(7_0);
//調(diào)用這個(gè)方法取消上面的布局動(dòng)畫設(shè)置,之后也會(huì)進(jìn)行上面方法的block回調(diào)
-?(void)cancelInteractiveTransitionNS_AVAILABLE_IOS(7_0);
//獲取分區(qū)數(shù)
-?(NSInteger)numberOfSections;
//獲取某一分區(qū)的item數(shù)
-?(NSInteger)numberOfItemsInSection:(NSInteger)section;
//下面兩個(gè)方法獲取item或者頭尾視圖的layout屬性,這個(gè)UICollectionViewLayoutAttributes對(duì)象
//存放著布局的相關(guān)數(shù)據(jù),可以用來做完全自定義布局,后面博客會(huì)介紹
-?(nullableUICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath;
-?(nullableUICollectionViewLayoutAttributes*)layoutAttributesForSupplementaryElementOfKind:(NSString*)kind?atIndexPath:(NSIndexPath*)indexPath;
//獲取某一點(diǎn)所在的indexpath位置
-?(nullableNSIndexPath*)indexPathForItemAtPoint:(CGPoint)point;
//獲取某個(gè)cell所在的indexPath
-?(nullableNSIndexPath*)indexPathForCell:(UICollectionViewCell*)cell;
//根據(jù)indexPath獲取cell
-?(nullableUICollectionViewCell*)cellForItemAtIndexPath:(NSIndexPath*)indexPath;
//獲取所有可見cell的數(shù)組
-?(NSArray<__kindofUICollectionViewCell*>?*)visibleCells;
//獲取所有可見cell的位置數(shù)組
-?(NSArray?*)indexPathsForVisibleItems;
//下面三個(gè)方法是iOS9中新添加的方法,用于獲取頭尾視圖
-?(UICollectionReusableView*)supplementaryViewForElementKind:(NSString*)elementKind?atIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(9_0);
-?(NSArray?*)visibleSupplementaryViewsOfKind:(NSString*)elementKindNS_AVAILABLE_IOS(9_0);
-?(NSArray?*)indexPathsForVisibleSupplementaryElementsOfKind:(NSString*)elementKindNS_AVAILABLE_IOS(9_0);
//使視圖滑動(dòng)到某一位置,可以帶動(dòng)畫效果
-?(void)scrollToItemAtIndexPath:(NSIndexPath*)indexPath?atScrollPosition:(UICollectionViewScrollPosition)scrollPosition?animated:(BOOL)animated;
//下面這些方法用于動(dòng)態(tài)添加,刪除,移動(dòng)某些分區(qū)獲取items
-?(void)insertSections:(NSIndexSet*)sections;
-?(void)deleteSections:(NSIndexSet*)sections;
-?(void)reloadSections:(NSIndexSet*)sections;
-?(void)moveSection:(NSInteger)section?toSection:(NSInteger)newSection;
-?(void)insertItemsAtIndexPaths:(NSArray?*)indexPaths;
-?(void)deleteItemsAtIndexPaths:(NSArray?*)indexPaths;
-?(void)reloadItemsAtIndexPaths:(NSArray?*)indexPaths;
-?(void)moveItemAtIndexPath:(NSIndexPath*)indexPath?toIndexPath:(NSIndexPath*)newIndexPath;
iOS流布局UICollectionView系列二——UICollectionView的代理方法
????????在上一篇博客中,介紹了最基本的UICollectionView的使用和其中我們常用的屬性和方法,也介紹了瀑布流布局的過程與思路,這篇博客來討論關(guān)于UICollectionView的代理方法的使用。
二、UICollectionViewDataSource協(xié)議
????????這個(gè)協(xié)議主要用于collectionView相關(guān)數(shù)據(jù)的處理,包含方法如下:
首先,有兩個(gè)方法是我們必須實(shí)現(xiàn)的:
設(shè)置每個(gè)分區(qū)的Item個(gè)數(shù)
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
設(shè)置返回每個(gè)item的屬性
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
下面的方法是可選實(shí)現(xiàn)的:
雖然這個(gè)方法是可選的,一般我們都會(huì)去實(shí)現(xiàn),設(shè)置分區(qū)數(shù)
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
對(duì)頭視圖或者尾視圖進(jìn)行設(shè)置
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
設(shè)置某個(gè)item是否可以被移動(dòng),返回NO則不能移動(dòng)
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
移動(dòng)item的時(shí)候,會(huì)調(diào)用這個(gè)方法
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
三、UICollectionViewDelegate協(xié)議
????????這個(gè)協(xié)議用來設(shè)置和處理collectionView的功能和一些邏輯,所有方法都是可選實(shí)現(xiàn):
是否允許某個(gè)Item的高亮,返回NO,則不能進(jìn)入高亮狀態(tài)
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
當(dāng)item高亮?xí)r觸發(fā)的方法
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
結(jié)束高亮狀態(tài)時(shí)觸發(fā)的方法
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;
是否可以選中某個(gè)Item,返回NO,則不能選中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
是否可以取消選中某個(gè)Item
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
已經(jīng)選中某個(gè)item時(shí)觸發(fā)的方法
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
取消選中某個(gè)Item時(shí)觸發(fā)的方法
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
將要加載某個(gè)Item時(shí)調(diào)用的方法
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
將要加載頭尾視圖時(shí)調(diào)用的方法
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
已經(jīng)展示某個(gè)Item時(shí)觸發(fā)的方法
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
已經(jīng)展示某個(gè)頭尾視圖時(shí)觸發(fā)的方法
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
這個(gè)方法設(shè)置是否展示長(zhǎng)按菜單
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
長(zhǎng)按菜單中可以觸發(fā)一下類復(fù)制粘貼的方法,效果如下:
這個(gè)方法用于設(shè)置要展示的菜單選項(xiàng)
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;
這個(gè)方法用于實(shí)現(xiàn)點(diǎn)擊菜單按鈕后的觸發(fā)方法,通過測(cè)試,只有copy,cut和paste三個(gè)方法可以使用
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;
通過下面的方式可以將點(diǎn)擊按鈕的方法名打印出來:
-(void)collectionView:(UICollectionView*)collectionView?performAction:(SEL)action?forItemAtIndexPath:(NSIndexPath*)indexPath?withSender:(id)sender{
NSLog(@"%@",NSStringFromSelector(action));
}
collectionView進(jìn)行重新布局時(shí)調(diào)用的方法
- (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;
iOS流布局UICollectionView系列三——使用FlowLayout進(jìn)行更靈活布局
????????前面的博客介紹了UICollectionView的相關(guān)方法和其協(xié)議中的方法,但對(duì)布局的管理類UICollectionViewFlowLayout沒有著重探討,這篇博客介紹關(guān)于布局的相關(guān)設(shè)置和屬性方法。通過layout的設(shè)置,我們可以編寫更加靈活的布局效果。
????????在第一篇博客中,通過UICollectionView,我們很輕松的完成了一個(gè)九宮格的布局,但是如此中規(guī)中矩的布局方式,有時(shí)候并不能滿足我們的需求,有時(shí)我們需要每一個(gè)Item展示不同的大小,代碼如下:
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
UICollectionViewFlowLayout*layout?=?[[UICollectionViewFlowLayoutalloc]init];
layout.scrollDirection?=UICollectionViewScrollDirectionVertical;
UICollectionView*collect?=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];
collect.delegate=self;
collect.dataSource=self;
[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];
??;
[self.view?addSubview:collect];
}
//設(shè)置每個(gè)item的大小,雙數(shù)的為50*50?單數(shù)的為100*100
-(CGSize)collectionView:(UICollectionView*)collectionView?layout:(UICollectionViewLayout*)collectionViewLayout?sizeForItemAtIndexPath:(NSIndexPath*)indexPath{
if(indexPath.row%2==0)?{
returnCGSizeMake(50,50);
}else{
returnCGSizeMake(100,100);
????}
}
//代理相應(yīng)方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{
return1;
}
-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{
return100;
}
-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
returncell;
}
效果如下:
現(xiàn)在的布局效果是不是炫酷了許多。
三、UICollectionViewFlowLayout相關(guān)屬性方法
????????UICollectionViewFlowLayout是系統(tǒng)提供給我們一個(gè)封裝好的流布局設(shè)置類,其中有一些布局屬性我們可以進(jìn)行設(shè)置:?
設(shè)置行與行之間的間距最小距離
@property(nonatomic) CGFloat minimumLineSpacing;
設(shè)置列與列之間的間距最小距離
@property(nonatomic) CGFloat minimumInteritemSpacing;
設(shè)置每個(gè)item的大小
@property(nonatomic) CGSize itemSize;
設(shè)置每個(gè)Item的估計(jì)大小,一般不需要設(shè)置
@property(nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);
設(shè)置布局方向
@property(nonatomic) UICollectionViewScrollDirection scrollDirection;
這個(gè)UICollectionViewScrollDirection的枚舉如下:
typedefNS_ENUM(NSInteger,UICollectionViewScrollDirection)?{
UICollectionViewScrollDirectionVertical,//水平布局
UICollectionViewScrollDirectionHorizontal//垂直布局
};
設(shè)置頭視圖尺寸大小
@property (nonatomic) CGSize headerReferenceSize;
設(shè)置尾視圖尺寸大小
@property (nonatomic) CGSize footerReferenceSize;
設(shè)置分區(qū)的EdgeInset
@property (nonatomic) UIEdgeInsets sectionInset;
這個(gè)屬性可以設(shè)置分區(qū)的偏移量,例如我們?cè)趧偛诺睦又刑砑尤缦略O(shè)置:
layout.sectionInset?=UIEdgeInsetsMake(20,20,20,20);
效果如下,會(huì)看到分區(qū)的邊界閃出了20像素
下面這兩個(gè)方法設(shè)置分區(qū)的頭視圖和尾視圖是否始終固定在屏幕上邊和下邊
?@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);
@property (nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);
四、動(dòng)態(tài)的配置layout的相關(guān)屬性UICollectionViewDelegateFlowLayout
????????上面的方法在創(chuàng)建FlowLayout時(shí)靜態(tài)的進(jìn)行設(shè)置,如果我們需要?jiǎng)討B(tài)的設(shè)置這些屬性,就像我們例子中的,每個(gè)item的大小會(huì)有差異,我們可以通過代理來實(shí)現(xiàn)。
? ? ? ? UICollectionViewDelegateFlowLayout是UICollectionViewDelegate的子協(xié)議,其中常用方法如下,我們只需要實(shí)現(xiàn)我們需要的即可:
動(dòng)態(tài)設(shè)置每個(gè)Item的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
動(dòng)態(tài)設(shè)置每個(gè)分區(qū)的EdgeInsets
?- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
動(dòng)態(tài)設(shè)置每行的間距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
動(dòng)態(tài)設(shè)置每列的間距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
動(dòng)態(tài)設(shè)置某個(gè)分區(qū)頭視圖大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
動(dòng)態(tài)設(shè)置某個(gè)分區(qū)尾視圖大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
iOS流布局UICollectionView系列四——自定義FlowLayout進(jìn)行瀑布流布局
????????前幾篇博客從UICollectionView的基礎(chǔ)應(yīng)用到設(shè)置UICollectionViewFlowLayout更加靈活的進(jìn)行布局,但都限制在系統(tǒng)為我們準(zhǔn)備好的布局框架中,還是有一些局限性,例如,如果我要進(jìn)行瀑布流似的不定高布局,前面的方法就很難滿足我們的需求了,如下:
這種布局無疑在app的應(yīng)用中更加廣泛,商品的展示,書架書目的展示,都會(huì)傾向于采用這樣的布局方式,當(dāng)然,通過自定義FlowLayout,我們也很容易實(shí)現(xiàn)。
????????首先,我們新建一個(gè)文件繼承于UICollectionViewFlowLayout:
@interfaceMyLayout:UICollectionViewFlowLayout
為了演示的方便,這里我不做更多的封裝,只添加一個(gè)屬性,直接讓外界將item個(gè)數(shù)傳遞進(jìn)來,我們把重心方法重寫布局的方法上:
@interfaceMyLayout:UICollectionViewFlowLayout
@property(nonatomic,assign)intitemCount;
@end
前面說過,UICollectionViewFlowLayout是一個(gè)專門用來管理collectionView布局的類,因此,collectionView在進(jìn)行UI布局前,會(huì)通過這個(gè)類的對(duì)象獲取相關(guān)的布局信息,F(xiàn)lowLayout類將這些布局信息全部存放在了一個(gè)數(shù)組中,數(shù)組中是UICollectionViewLayoutAttributes類,這個(gè)類是對(duì)item布局的具體設(shè)置,以后咱們?cè)谟懻撨@個(gè)類。總之,F(xiàn)lowLayout類將每個(gè)item的位置等布局信息放在一個(gè)數(shù)組中,在collectionView布局時(shí),會(huì)調(diào)用FlowLayout類layoutAttributesForElementsInRect:方法來獲取這個(gè)布局配置數(shù)組。因此,我們需要重寫這個(gè)方法,返回我們自定義的配置數(shù)組,另外,F(xiàn)lowLayout類在進(jìn)行布局之前,會(huì)調(diào)用prepareLayout方法,所以我們可以重寫這個(gè)方法,在里面對(duì)我們的自定義配置數(shù)據(jù)進(jìn)行一些設(shè)置。
簡(jiǎn)單來說,自定義一個(gè)FlowLayout布局類就是兩個(gè)步驟:
1、設(shè)計(jì)好我們的布局配置數(shù)據(jù) prepareLayout方法中
2、返回我們的配置數(shù)組?layoutAttributesForElementsInRect方法中
示例代碼如下:
@implementationMyLayout
{
//這個(gè)數(shù)組就是我們自定義的布局配置數(shù)組
NSMutableArray*?_attributeAttay;
}
//數(shù)組的相關(guān)設(shè)置在這個(gè)方法中
//布局前的準(zhǔn)備會(huì)調(diào)用這個(gè)方法
-(void)prepareLayout{
_attributeAttay?=?[[NSMutableArrayalloc]init];
[superprepareLayout];
//演示方便?我們?cè)O(shè)置為靜態(tài)的2列
//計(jì)算每一個(gè)item的寬度
floatWIDTH?=?([UIScreenmainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-self.minimumInteritemSpacing)/2;
//定義數(shù)組保存每一列的高度
//這個(gè)數(shù)組的主要作用是保存每一列的總高度,這樣在布局時(shí),我們可以始終將下一個(gè)Item放在最短的列下面
CGFloatcolHight[2]={self.sectionInset.top,self.sectionInset.bottom};
//itemCount是外界傳進(jìn)來的item的個(gè)數(shù)?遍歷來設(shè)置每一個(gè)item的布局
for(inti=0;?i<_itemCount;?i++)?{
//設(shè)置每個(gè)item的位置等相關(guān)屬性
NSIndexPath*index?=?[NSIndexPathindexPathForItem:i?inSection:0];
//創(chuàng)建一個(gè)布局屬性類,通過indexPath來創(chuàng)建
UICollectionViewLayoutAttributes*?attris?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:index];
//隨機(jī)一個(gè)高度?在40——190之間
CGFloathight?=?arc4random()%150+40;
//哪一列高度小?則放到那一列下面
//標(biāo)記最短的列
intwidth=0;
if(colHight[0]
//將新的item高度加入到短的一列
colHight[0]?=?colHight[0]+hight+self.minimumLineSpacing;
width=0;
}else{
colHight[1]?=?colHight[1]+hight+self.minimumLineSpacing;
width=1;
????????}
//設(shè)置item的位置
attris.frame?=CGRectMake(self.sectionInset.left+(self.minimumInteritemSpacing+WIDTH)*width,?colHight[width]-hight-self.minimumLineSpacing,?WIDTH,?hight);
????????[_attributeAttay?addObject:attris];
????}
//設(shè)置itemSize來確保滑動(dòng)范圍的正確?這里是通過將所有的item高度平均化,計(jì)算出來的(以最高的列位標(biāo)準(zhǔn))
if(colHight[0]>colHight[1])?{
self.itemSize?=CGSizeMake(WIDTH,?(colHight[0]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);
}else{
self.itemSize?=CGSizeMake(WIDTH,?(colHight[1]-self.sectionInset.top)*2/_itemCount-self.minimumLineSpacing);
????}
}
//這個(gè)方法中返回我們的布局?jǐn)?shù)組
-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{
return_attributeAttay;
}
@end
自定義完成FlowLayout后,我們?cè)赩iewController中進(jìn)行使用:
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
????MyLayout?*?layout?=?[[MyLayout?alloc]init];
layout.scrollDirection?=UICollectionViewScrollDirectionVertical;
layout.itemCount=100;
UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];
collect.delegate=self;
collect.dataSource=self;
[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];
[self.view?addSubview:collect];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{
return1;
}
-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{
return100;
}
-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
returncell;
}
運(yùn)行效果就是我們引言中的截圖。
三、UICollectionViewLayoutAttributes類中我們可以配置的屬性
????????通過上面的例子,我們可以了解,collectionView的item布局其實(shí)是LayoutAttributes類具體配置的,這個(gè)類可以配置的布局屬性不止是frame這么簡(jiǎn)單,其中還有許多屬性:
//配置item的布局位置
@property(nonatomic)CGRectframe;
//配置item的中心
@property(nonatomic)CGPointcenter;
//配置item的尺寸
@property(nonatomic)CGSizesize;
//配置item的3D效果
@property(nonatomic)CATransform3Dtransform3D;
//配置item的bounds
@property(nonatomic)CGRectboundsNS_AVAILABLE_IOS(7_0);
//配置item的旋轉(zhuǎn)
@property(nonatomic)CGAffineTransformtransformNS_AVAILABLE_IOS(7_0);
//配置item的alpha
@property(nonatomic)CGFloatalpha;
//配置item的z坐標(biāo)
@property(nonatomic)NSIntegerzIndex;//?default?is?0
//配置item的隱藏
@property(nonatomic,getter=isHidden)BOOLhidden;
//item的indexpath
@property(nonatomic,strong)NSIndexPath*indexPath;
//獲取item的類型
@property(nonatomic,readonly)UICollectionElementCategoryrepresentedElementCategory;
@property(nonatomic,readonly,nullable)NSString*representedElementKind;
//一些創(chuàng)建方法
+?(instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath*)indexPath;
+?(instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString*)elementKind?withIndexPath:(NSIndexPath*)indexPath;
+?(instancetype)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind?withIndexPath:(NSIndexPath*)indexPath;
通過上面的屬性,可以布局出各式各樣的炫酷效果,正如一句話:沒有做不到,只有想不到。
iOS流布局UICollectionView系列五——圓環(huán)布局的實(shí)現(xiàn)
????????前邊的幾篇博客,我們了解了UICollectionView的基本用法以及一些擴(kuò)展,在不定高的瀑布流布局中,我們發(fā)現(xiàn),可以通過設(shè)置具體的布局屬性類UICollectionViewLayoutAttributes來設(shè)置設(shè)置每個(gè)item的具體位置,我們可以再擴(kuò)展一下,如果位置我們可以自由控制,那個(gè)布局我們也可以更加靈活,就比如創(chuàng)建一個(gè)如下的circleLayout:
這種布局方式在apple的官方文檔中也有介紹,是UICollectionView的一個(gè)應(yīng)用示例。
? ? ? ? 先自定義一個(gè)layout類,這個(gè)類繼承于UICollectionViewLayout,UICollectionLayout是一個(gè)布局抽象基類,我們要使用自定義的布局方式,必須將其子類化,可能你還記得,我們?cè)谶M(jìn)行瀑布流布局的時(shí)候使用過UICollectionViewFlowLayout類,這個(gè)類就是繼承于UICollectionViewLayout類,系統(tǒng)為我們實(shí)現(xiàn)好的一個(gè)布局方案。
@interfaceMyLayout:UICollectionViewLayout
//這個(gè)int值存儲(chǔ)有多少個(gè)item
@property(nonatomic,assign)intitemCount;
@end
我們需要重寫這個(gè)類的三個(gè)方法,來進(jìn)行圓環(huán)布局的設(shè)置,首先是prepareLayout,為布局做一些準(zhǔn)備工作,使用collectionViewContentSize來設(shè)置內(nèi)容的區(qū)域大小,最后使用layoutAttributesForElementsInRect方法來返回我們的布局信息字典,這個(gè)和前面瀑布流布局的思路是一樣的:
@implementationMyLayout
{
NSMutableArray*?_attributeAttay;
}
-(void)prepareLayout{
[superprepareLayout];
//獲取item的個(gè)數(shù)
_itemCount?=?(int)[self.collectionView?numberOfItemsInSection:0];
_attributeAttay?=?[[NSMutableArrayalloc]init];
//先設(shè)定大圓的半徑?取長(zhǎng)和寬最短的
CGFloatradius?=?MIN(self.collectionView.frame.size.width,self.collectionView.frame.size.height)/2;
//計(jì)算圓心位置
CGPointcenter?=CGPointMake(self.collectionView.frame.size.width/2,self.collectionView.frame.size.height/2);
//設(shè)置每個(gè)item的大小為50*50?則半徑為25
for(inti=0;?i<_itemCount;?i++)?{
UICollectionViewLayoutAttributes*?attris?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:[NSIndexPathindexPathForItem:i?inSection:0]];
//設(shè)置item大小
attris.size?=CGSizeMake(50,50);
//計(jì)算每個(gè)item的圓心位置
/*
?????????.
?????????.?.
?????????.???.?r
?????????.?????.
?????????.........
?????????*/
//計(jì)算每個(gè)item中心的坐標(biāo)
//算出的x?y值還要減去item自身的半徑大小
floatx?=?center.x+cosf(2*M_PI/_itemCount*i)*(radius-25);
floaty?=?center.y+sinf(2*M_PI/_itemCount*i)*(radius-25);
attris.center?=CGPointMake(x,?y);
????????[_attributeAttay?addObject:attris];
????}
}
//設(shè)置內(nèi)容區(qū)域的大小
-(CGSize)collectionViewContentSize{
returnself.collectionView.frame.size;
}
//返回設(shè)置數(shù)組
-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{
return_attributeAttay;
}
在viewController中代碼如下:
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
????MyLayout?*?layout?=?[[MyLayout?alloc]init];
UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];
collect.delegate=self;
collect.dataSource=self;
[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];
[self.view?addSubview:collect];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{
return1;
}
-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{
return10;
}
-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.layer.masksToBounds?=YES;
cell.layer.cornerRadius?=25;
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
returncell;
}
如上非常簡(jiǎn)單的一些邏輯控制,我們就實(shí)現(xiàn)哦圓環(huán)布局,隨著item的多少,布局會(huì)自動(dòng)調(diào)整,如果不是UICollectionView的功勞,實(shí)現(xiàn)這樣的功能,我們可能要寫上一陣子了^_^。
iOS流布局UICollectionView系列六——將布局從平面應(yīng)用到空間
????????前面,我們將布局由線性的瀑布流布局?jǐn)U展到了圓環(huán)布局,這使我們使用UICollectionView的布局思路大大邁進(jìn)了一步,這次,我們玩的更加炫一些,想辦法將布局應(yīng)用到空間。之前在管理布局的item的具體屬性的類UICollectionViewLayoutAttributrs類中,有transform3D這個(gè)屬性,通過這個(gè)屬性的設(shè)置,我們真的可以在空間的坐標(biāo)系中進(jìn)行布局設(shè)計(jì)。iOS系統(tǒng)的控件中,也并非沒有這樣的先例,UIPickerView就是很好的一個(gè)實(shí)例,這篇博客,我們就通過使用UICollectionView實(shí)現(xiàn)一個(gè)類似系統(tǒng)的UIPickerView的布局視圖,來體會(huì)UICollectionView在3D控件布局的魅力。系統(tǒng)的pickerView效果如下:
二、先來實(shí)現(xiàn)一個(gè)炫酷的滾輪空間布局
????????萬丈的高樓也是由一磚一瓦堆砌而成,在我們完全模擬系統(tǒng)pickerView前,我們應(yīng)該先將視圖的布局?jǐn)[放這一問題解決。我們依然來創(chuàng)建一個(gè)類,繼承于UICollectionViewLayout:
@interfaceMyLayout:UICollectionViewLayout
@end
對(duì)于.m文件的內(nèi)容,前幾篇博客中我們都是在prepareLayout中進(jìn)行布局的靜態(tài)設(shè)置,那是因?yàn)槲覀兦皫灼┛椭械牟季侄际庆o態(tài)的,布局并不會(huì)隨著我們的手勢(shì)操作而發(fā)生太大的變化,因此我們?nèi)吭趐repareLayout中一次配置完了。而我們這次要討論的布局則不同,pickerView會(huì)隨著我們手指的拖動(dòng)而進(jìn)行滾動(dòng),因此UICollectionView中的每一個(gè)item的布局是在不斷變化的,所以這次,我們采用動(dòng)態(tài)配置的方式,在layoutAttributesForItemAtIndexPath方法中進(jìn)行每個(gè)item的布局屬性設(shè)置。
????????至于layoutAttributesForItemAtIndexPath方法,它也是UICollectionViewLayout類中的方法,用于我們自定義時(shí)進(jìn)行重寫,至于為什么動(dòng)態(tài)布局要在這里面配置item的布局屬性,后面我們會(huì)了解到。
? ? ? ? 在編寫我們的布局類之前,先做好準(zhǔn)備工作,在viewController中,實(shí)現(xiàn)如下代碼:
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
????MyLayout?*?layout?=?[[MyLayout?alloc]init];
UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];
collect.delegate=self;
collect.dataSource=self;
[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];
[self.view?addSubview:collect];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{
return1;
}
-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{
return10;
}
-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
UILabel*?label?=?[[UILabelalloc]initWithFrame:CGRectMake(0,0,250,80)];
label.text?=?[NSStringstringWithFormat:@"我是第%ld行",(long)indexPath.row];
????[cell.contentView?addSubview:label];
returncell;
}
上面我創(chuàng)建了10個(gè)Item,并且在每個(gè)Item上添加了一個(gè)標(biāo)簽,標(biāo)寫是第幾行。
在我們自定義的布局類中重寫layoutAttributesForElementsInRect,在其中返回我們的布局?jǐn)?shù)組:
-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray*?attributes?=?[[NSMutableArrayalloc]init];
//遍歷設(shè)置每個(gè)item的布局屬性
for(inti=0;?i<[self.collectionView?numberOfItemsInSection:0];?i++)?{
[attributes?addObject:[selflayoutAttributesForItemAtIndexPath:[NSIndexPathindexPathForItem:i?inSection:0]]];
????}
returnattributes;
}
之后,在我們布局類中重寫layoutAttributesForItemAtIndexPath方法:
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{
//創(chuàng)建一個(gè)item布局屬性類
UICollectionViewLayoutAttributes*?atti?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:indexPath];
//獲取item的個(gè)數(shù)
intitemCounts?=?(int)[self.collectionView?numberOfItemsInSection:0];
//設(shè)置每個(gè)item的大小為260*100
atti.size?=CGSizeMake(260,100);
/*
???后邊介紹的代碼添加在這里
???*/
returnatti;
}
上面的代碼中,我們什么都沒有做,下面我們一步步來實(shí)現(xiàn)3D的滾輪效果。
首先,我們先將所有的item的位置都設(shè)置為collectionView的中心:
atti.center?=CGPointMake(self.collectionView.frame.size.width/2,self.collectionView.frame.size.height/2);
這時(shí),如果我們運(yùn)行程序的話,所有item都將一層層貼在屏幕的中央,如下:
很丑對(duì)吧,之后我們來設(shè)置每個(gè)item的3D效果,在上面的布局方法中添加如下代碼:
//創(chuàng)建一個(gè)transform3D類
//CATransform3D是一個(gè)類似矩陣的結(jié)構(gòu)體
//CATransform3DIdentity創(chuàng)建空得矩陣
CATransform3Dtrans3D?=CATransform3DIdentity;
//這個(gè)值設(shè)置的是透視度,影響視覺離投影平面的距離
trans3D.m34?=-1/900.0;
//下面這些屬性?后面會(huì)具體介紹
//這個(gè)是3D滾輪的半徑
CGFloatradius?=50/tanf(M_PI*2/itemCounts/2);
//計(jì)算每個(gè)item應(yīng)該旋轉(zhuǎn)的角度
CGFloatangle?=?(float)(indexPath.row)/itemCounts*M_PI*2;
//這個(gè)方法返回一個(gè)新的CATransform3D對(duì)象,在原來的基礎(chǔ)上進(jìn)行旋轉(zhuǎn)效果的追加
//第一個(gè)參數(shù)為旋轉(zhuǎn)的弧度,后三個(gè)分別對(duì)應(yīng)x,y,z軸,我們需要以x軸進(jìn)行旋轉(zhuǎn)
trans3D?=CATransform3DRotate(trans3D,?angle,1.0,0,0);
//進(jìn)行設(shè)置
????atti.transform3D?=?trans3D;
????????對(duì)于上面的radius屬性,運(yùn)用了一些簡(jiǎn)單的幾何和三角函數(shù)的知識(shí)。如果我們將系統(tǒng)的pickerView沿著y軸旋轉(zhuǎn)90°,你會(huì)發(fā)現(xiàn)側(cè)面的它是一個(gè)規(guī)則的正多邊形,這里的radius就是這個(gè)多邊形中心到其邊的垂直距離,也是內(nèi)切圓的半徑,所有的item拼成了一個(gè)正多邊形,示例如下:
通過簡(jiǎn)單的數(shù)學(xué)知識(shí),h/2弦對(duì)應(yīng)的角的弧度為2*pi/(邊數(shù))/2,在根據(jù)三角函數(shù)相關(guān)知識(shí)可知,這個(gè)角的正切值為h/2/radius,這就是我們r(jià)adius的由來。?
????????對(duì)于angle屬性,它是每一個(gè)item的x軸旋轉(zhuǎn)度數(shù),如果我們將所有item的中心都放在一點(diǎn),通過旋轉(zhuǎn)讓它們散開如下圖所示:
每個(gè)item旋轉(zhuǎn)的弧度就是其索引/(2*pi)。
通過上面的設(shè)置,我們?cè)龠\(yùn)行代碼,效果如下:
仔細(xì)觀察我們可以發(fā)現(xiàn),item以x中軸線進(jìn)行了旋轉(zhuǎn)平均布局,側(cè)面的效果就是我們上面的簡(jiǎn)筆畫那樣,下面要進(jìn)行我們的第三步了,將這個(gè)item,全部沿著其Z軸向前拉,就可以成為我們滾輪的效果,示例圖如下:
我們繼續(xù)在剛才的代碼后面添加這行代碼:
//這個(gè)方法也返回一個(gè)transform3D對(duì)象,追加平移效果,后面三個(gè)參數(shù),對(duì)應(yīng)平移的x,y,z軸,我們沿z軸平移
trans3D?=CATransform3DTranslate(trans3D,0,0,?radius);
再次運(yùn)行,效果如下:
布局的效果我們已經(jīng)完成了,離成功很近了對(duì)吧,只是現(xiàn)在的布局是靜態(tài)的,我們不能滑動(dòng)這個(gè)滾輪,我們還需要用動(dòng)態(tài)滑動(dòng)做一些處理。
????????????通過上面的努力,我們已經(jīng)靜態(tài)布局出了一個(gè)類似pickerView的滾輪,現(xiàn)在我們?cè)賮硖砑踊瑒?dòng)滾動(dòng)的效果
????????首先,我們需要給collectionView一個(gè)滑動(dòng)的范圍,我們以一屏collectionView的滑動(dòng)距離來當(dāng)做滾輪滾動(dòng)一下的參照,我們?cè)诓季诸愔械娜缦路椒ㄖ蟹祷鼗瑒?dòng)區(qū)域:
-(CGSize)collectionViewContentSize{
returnCGSizeMake(self.collectionView.frame.size.width,self.collectionView.frame.size.height*[self.collectionView?numberOfItemsInSection:0]);
}
這時(shí)我們的collectionView已經(jīng)可以進(jìn)行滑動(dòng),但是并不是我們想要的效果,滾輪并沒有滾動(dòng),而是隨著滑動(dòng)出了屏幕,因此,我們需要在滑動(dòng)的時(shí)候不停的動(dòng)態(tài)布局,將滾輪始終固定在collectionView的中心,先需要在布局類中實(shí)現(xiàn)如下方法:
//返回yes,則一有變化就會(huì)刷新布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
returnYES;
}
將上面的布局的中心點(diǎn)設(shè)置加上一個(gè)動(dòng)態(tài)的偏移量:
atti.center?=CGPointMake(self.collectionView.frame.size.width/2,self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);
現(xiàn)在在運(yùn)行,會(huì)發(fā)現(xiàn)滾輪會(huì)隨著滑動(dòng)始終固定在中間,但是還是不如人意,滾輪并沒有轉(zhuǎn)動(dòng)起來,我們還需要?jiǎng)討B(tài)的設(shè)置每個(gè)item的旋轉(zhuǎn)角度,這樣連續(xù)看起來,滾輪就轉(zhuǎn)了起來,在上面設(shè)置布局的方法中,我們?cè)谔砑右恍┨幚恚?/p>
//獲取當(dāng)前的偏移量
floatoffset?=self.collectionView.contentOffset.y;
//在角度設(shè)置上,添加一個(gè)偏移角度
floatangleOffset?=?offset/self.collectionView.frame.size.height;
CGFloatangle?=?(float)(indexPath.row+angleOffset)/itemCounts*M_PI*2;
再看看效果,沒錯(cuò),就是這么簡(jiǎn)單,滾輪已經(jīng)轉(zhuǎn)了起來。
????????我們?cè)龠M(jìn)一步,如果滾動(dòng)可以循環(huán),這個(gè)控件將更加炫酷,添加這樣的邏輯也很簡(jiǎn)單,通過監(jiān)測(cè)scrollView的偏移量,我們可以對(duì)齊進(jìn)行處理,因?yàn)閏ollectionView繼承于scrollView,我們可以直接在ViewController中實(shí)現(xiàn)其代理方法,如下:
-(void)scrollViewDidScroll:(UIScrollView*)scrollView{
//小于半屏?則放到最后一屏多半屏
if(scrollView.contentOffset.y<200)?{
scrollView.contentOffset?=CGPointMake(0,?scrollView.contentOffset.y+10*400);
//大于最后一屏多一屏?放回第一屏
}elseif(scrollView.contentOffset.y>11*400){
scrollView.contentOffset?=CGPointMake(0,?scrollView.contentOffset.y-10*400);
????}
}
因?yàn)樵蹅兊沫h(huán)狀布局,上面的邏輯剛好可以無縫對(duì)接,但是會(huì)有新的問題,一開始運(yùn)行,滾輪就是出現(xiàn)在最后一個(gè)item的位置,而不是第一個(gè),并且有些相關(guān)的地方,我們也需要一些適配:
在viewController中:
//一開始將collectionView的偏移量設(shè)置為1屏的偏移量
collect.contentOffset?=CGPointMake(0,400);
在layout類中:
//將滾動(dòng)范圍設(shè)置為(item總數(shù)+2)*每屏高度?
-(CGSize)collectionViewContentSize{
returnCGSizeMake(self.collectionView.frame.size.width,self.collectionView.frame.size.height*([self.collectionView?numberOfItemsInSection:0]+2));
}
//將計(jì)算的具體item角度向前遞推一個(gè)
CGFloatangle?=?(float)(indexPath.row+angleOffset-1)/itemCounts*M_PI*2;
OK,我們終于大功告成了,可以發(fā)現(xiàn),實(shí)現(xiàn)這樣一個(gè)布局效果炫酷的控件,代碼其實(shí)并沒有多少,相比,數(shù)學(xué)邏輯要比編寫代碼本身困難,這十分類似數(shù)學(xué)中的幾何問題,如果你弄清了邏輯,解決是分分鐘的事,我們可以通過這樣的一個(gè)思路,設(shè)計(jì)更多3D或者平面特效的布局方案,抽獎(jiǎng)的轉(zhuǎn)動(dòng)圓盤,書本的翻頁,甚至立體的標(biāo)簽云,UICollectionView都可以實(shí)現(xiàn),這篇博客中的代碼在下面的連接中,疏漏之處,歡迎指正!
http://pan.baidu.com/s/1jGCmbKM
iOS流布局UICollectionView系列七——三維中的球型布局
????????通過6篇的博客,從平面上最簡(jiǎn)單的規(guī)則擺放的布局,到不規(guī)則的瀑布流布局,再到平面中的圓環(huán)布局,我們突破了線性布局的局限,在后面,我們將布局?jǐn)U展到了空間,在Z軸上進(jìn)行了平移,我們實(shí)現(xiàn)了一個(gè)類似UIPickerView的布局模型,其實(shí)我們還可以再進(jìn)一步,類比于平面布局,picKerView只是線性排列布局在空間上的旋轉(zhuǎn)與平移,這次,我們更加充分了利用一下空間的尺寸,來設(shè)計(jì)一個(gè)圓球的布局模型。
????????在viewController中先實(shí)現(xiàn)一些準(zhǔn)備代碼:
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
????MyLayout?*?layout?=?[[MyLayout?alloc]init];
UICollectionView*?collect??=?[[UICollectionViewalloc]initWithFrame:CGRectMake(0,0,320,400)?collectionViewLayout:layout];
collect.delegate=self;
collect.dataSource=self;
//這里設(shè)置的偏移量是為了無縫進(jìn)行循環(huán)的滾動(dòng),具體在上一篇博客中有解釋
collect.contentOffset?=CGPointMake(320,400);
[collect?registerClass:[UICollectionViewCellclass]?forCellWithReuseIdentifier:@"cellid"];
[self.view?addSubview:collect];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView{
return1;
}
//我們返回30的標(biāo)簽
-(NSInteger)collectionView:(UICollectionView*)collectionView?numberOfItemsInSection:(NSInteger)section{
return30;
}
-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionView?cellForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewCell*?cell??=?[collectionView?dequeueReusableCellWithReuseIdentifier:@"cellid"forIndexPath:indexPath];
cell.backgroundColor?=?[UIColorcolorWithRed:arc4random()%255/255.0green:arc4random()%255/255.0blue:arc4random()%255/255.0alpha:1];
UILabel*?label?=?[[UILabelalloc]initWithFrame:CGRectMake(0,0,30,30)];
label.text?=?[NSStringstringWithFormat:@"%ld",(long)indexPath.row];
????[cell.contentView?addSubview:label];
returncell;
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
//這里對(duì)滑動(dòng)的contentOffset進(jìn)行監(jiān)控,實(shí)現(xiàn)循環(huán)滾動(dòng)
-(void)scrollViewDidScroll:(UIScrollView*)scrollView{
if(scrollView.contentOffset.y<200)?{
scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x,?scrollView.contentOffset.y+10*400);
}elseif(scrollView.contentOffset.y>11*400){
scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x,?scrollView.contentOffset.y-10*400);
????}
if(scrollView.contentOffset.x<160)?{
scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x+10*320,scrollView.contentOffset.y);
}elseif(scrollView.contentOffset.x>11*320){
scrollView.contentOffset?=CGPointMake(scrollView.contentOffset.x-10*320,scrollView.contentOffset.y);
????}
}
這里面的代碼比較上一篇博客中的并沒有什么大的改動(dòng),只是做了橫坐標(biāo)的兼容。
在我們的layout類中,將代碼修改成如下:
-(void)prepareLayout{
[superprepareLayout];
}
//返回的滾動(dòng)范圍增加了對(duì)x軸的兼容
-(CGSize)collectionViewContentSize{
returnCGSizeMake(self.collectionView.frame.size.width*([self.collectionView?numberOfItemsInSection:0]+2),self.collectionView.frame.size.height*([self.collectionView?numberOfItemsInSection:0]+2));
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
returnYES;
}
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewLayoutAttributes*?atti?=?[UICollectionViewLayoutAttributeslayoutAttributesForCellWithIndexPath:indexPath];
//獲取item的個(gè)數(shù)
intitemCounts?=?(int)[self.collectionView?numberOfItemsInSection:0];
atti.center?=CGPointMake(self.collectionView.frame.size.width/2+self.collectionView.contentOffset.x,self.collectionView.frame.size.height/2+self.collectionView.contentOffset.y);
atti.size?=CGSizeMake(30,30);
CATransform3Dtrans3D?=CATransform3DIdentity;
trans3D.m34?=-1/900.0;
CGFloatradius?=15/tanf(M_PI*2/itemCounts/2);
//根據(jù)偏移量?改變角度
//添加了一個(gè)x的偏移量
floatoffsety?=self.collectionView.contentOffset.y;
floatoffsetx?=self.collectionView.contentOffset.x;
//分別計(jì)算偏移的角度
floatangleOffsety?=?offsety/self.collectionView.frame.size.height;
floatangleOffsetx?=?offsetx/self.collectionView.frame.size.width;
CGFloatangle1?=?(float)(indexPath.row+angleOffsety-1)/itemCounts*M_PI*2;
//x,y的默認(rèn)方向相反
CGFloatangle2?=?(float)(indexPath.row-angleOffsetx-1)/itemCounts*M_PI*2;
//這里我們進(jìn)行四個(gè)方向的排列
if(indexPath.row%4==1)?{
trans3D?=CATransform3DRotate(trans3D,?angle1,1.0,0,0);
}elseif(indexPath.row%4==2){
trans3D?=CATransform3DRotate(trans3D,?angle2,0,1,0);
}elseif(indexPath.row%4==3){
trans3D?=CATransform3DRotate(trans3D,?angle1,0.5,0.5,0);
}else{
trans3D?=CATransform3DRotate(trans3D,?angle1,0.5,-0.5,0);
????}
trans3D?=CATransform3DTranslate(trans3D,0,0,?radius);
????atti.transform3D?=?trans3D;
returnatti;
}
-(NSArray?*)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray*?attributes?=?[[NSMutableArrayalloc]init];
//遍歷設(shè)置每個(gè)item的布局屬性
for(inti=0;?i<[self.collectionView?numberOfItemsInSection:0];?i++)?{
[attributes?addObject:[selflayoutAttributesForItemAtIndexPath:[NSIndexPathindexPathForItem:i?inSection:0]]];
????}
returnattributes;
}
布局效果如下:
滑動(dòng)屏幕,這個(gè)圓球是可以進(jìn)行滾動(dòng)的。
TIP:這里我們只平均分配了四個(gè)方向上的布局,如果item更加小也更加多,我們可以分配到更多的方向上,使球體更加充實(shí)。