一虫溜、簡介
UICollectionView是iOS6之后引入的一個(gè)新的UI控件,它和UITableView有著諸多的相似之處,其中許多代理方法都十分類似香缺。簡單來說,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è)最簡單的九宮格類布局
在了解UICollectionView的更多屬性前颂跨,我們先來使用其進(jìn)行一個(gè)最簡單的流布局試試看,在controller的viewDidLoad中添加如下代碼:
//創(chuàng)建一個(gè)layout布局類
UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc]init];
//設(shè)置布局方向?yàn)榇怪绷鞑季?layout.scrollDirection = UICollectionViewScrollDirectionVertical;
//設(shè)置每個(gè)item的大小為100*100
layout.itemSize = CGSizeMake(100, 100);
//創(chuàng)建collectionView 通過一個(gè)布局策略layout來創(chuàng)建
UICollectionView * collect = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
//代理設(shè)置
collect.delegate=self;
collect.dataSource=self;
//注冊item類型 這里使用系統(tǒng)的類型
[collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
[self.view addSubview:collect];
這里有一點(diǎn)需要注意扯饶,collectionView在完成代理回調(diào)前恒削,必須注冊一個(gè)cell聚请,類似如下:
[collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
這和tableView有些類似抬纸,又有些不同亏娜,因?yàn)閠ableView除了注冊cell的方法外恒傻,還可以通過臨時(shí)創(chuàng)建來做:
//tableView在從復(fù)用池中取cell的時(shí)候捷绒,有如下兩種方法
//使用這種方式如果復(fù)用池中無帮非,是可以返回nil的欧啤,我們在臨時(shí)創(chuàng)建即可
- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
//6.0后使用如下的方法直接從注冊的cell類獲取創(chuàng)建鼓择,如果沒有注冊 會(huì)崩潰
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_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 = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
return cell;
}
//這樣做會(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 = [[UICollectionViewCell alloc]init];
return cell;
}
上面錯(cuò)誤的方式會(huì)崩潰掖举,信息如下快骗,讓我們使用從復(fù)用池中取cell的方式:
上面的設(shè)置完成后,我們來實(shí)現(xiàn)如下幾個(gè)代理方法:
這里與TableView的回調(diào)方式十分類似
//返回分區(qū)個(gè)數(shù)
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
//返回每個(gè)分區(qū)的item個(gè)數(shù)
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 10;
}
//返回每個(gè)item
-(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];
return cell;
}
效果如下:
同樣塔次,如果內(nèi)容的大小超出一屏方篮,和tableView類似是可以進(jìn)行視圖滑動(dòng)的。
還有一點(diǎn)細(xì)節(jié)励负,我們在上面設(shè)置布局方式的時(shí)候設(shè)置了垂直布局:
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
//這個(gè)是水平布局
//layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
這樣系統(tǒng)會(huì)在一行充滿后進(jìn)行第二行的排列藕溅,如果設(shè)置為水平布局,則會(huì)在一列充滿后继榆,進(jìn)行第二列的布局巾表,這種方式也被稱為流式布局
三汁掠、UICollectionView中的常用方法和屬性
//通過一個(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 <UICollectionViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;
//從一個(gè)class或者xib文件進(jìn)行cell(item)的注冊
- (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;
//下面兩個(gè)方法與上面相似,這里注冊的是頭視圖或者尾視圖的類
//其中第二個(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:(nullable Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(nullable UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;
//這兩個(gè)方法是從復(fù)用池中取出cell或者頭尾視圖
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
//設(shè)置是否允許選中 默認(rèn)yes
@property (nonatomic) BOOL allowsSelection;
//設(shè)置是否允許多選 默認(rèn)no
@property (nonatomic) BOOL allowsMultipleSelection;
//獲取所有選中的item的位置信息
- (nullable NSArray<NSIndexPath *> *)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:(nullable NSIndexPath *)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)(BOOL finished))completion NS_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:(nullable UICollectionViewLayoutInteractiveTransitionCompletion)completion NS_AVAILABLE_IOS(7_0);
//準(zhǔn)備好動(dòng)畫設(shè)置后,我們需要調(diào)用下面的方法進(jìn)行布局動(dòng)畫的展示董虱,之后會(huì)調(diào)用上面方法的block回調(diào)
- (void)finishInteractiveTransition NS_AVAILABLE_IOS(7_0);
//調(diào)用這個(gè)方法取消上面的布局動(dòng)畫設(shè)置扼鞋,之后也會(huì)進(jìn)行上面方法的block回調(diào)
- (void)cancelInteractiveTransition NS_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ì)介紹
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
//獲取某一點(diǎn)所在的indexpath位置
- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point;
//獲取某個(gè)cell所在的indexPath
- (nullable NSIndexPath *)indexPathForCell:(UICollectionViewCell *)cell;
//根據(jù)indexPath獲取cell
- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;
//獲取所有可見cell的數(shù)組
- (NSArray<__kindof UICollectionViewCell *> *)visibleCells;
//獲取所有可見cell的位置數(shù)組
- (NSArray<NSIndexPath *> *)indexPathsForVisibleItems;
//下面三個(gè)方法是iOS9中新添加的方法,用于獲取頭尾視圖
- (UICollectionReusableView *)supplementaryViewForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
- (NSArray<UICollectionReusableView *> *)visibleSupplementaryViewsOfKind:(NSString *)elementKind NS_AVAILABLE_IOS(9_0);
- (NSArray<NSIndexPath *> *)indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)elementKind NS_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<NSIndexPath *> *)indexPaths;
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)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è)置是否展示長按菜單
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
長按菜單中可以觸發(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ā)方法,通過測試譬圣,只有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è)置绳姨,我們可以編寫更加靈活的布局效果。
二阔挠、將九宮格式的布局進(jìn)行升級(jí)
在第一篇博客中飘庄,通過UICollectionView,我們很輕松的完成了一個(gè)九宮格的布局购撼,但是如此中規(guī)中矩的布局方式跪削,有時(shí)候并不能滿足我們的需求,有時(shí)我們需要每一個(gè)Item展示不同的大小迂求,代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
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];
}
//設(shè)置每個(gè)item的大小碾盐,雙數(shù)的為50*50 單數(shù)的為100*100
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row%2==0) {
return CGSizeMake(50, 50);
}else{
return CGSizeMake(100, 100);
}
}
//代理相應(yīng)方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 100;
}
-(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];
return cell;
}
效果如下:
現(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的枚舉如下:
typedef NS_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ū)的偏移量凌盯,例如我們在剛才的例子中添加如下設(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)。
二后频、進(jìn)行自定義瀑布流布局
首先,我們新建一個(gè)文件繼承于UICollectionViewFlowLayout:
@interface MyLayout : UICollectionViewFlowLayout
為了演示的方便帝簇,這里我不做更多的封裝丧肴,只添加一個(gè)屬性芋浮,直接讓外界將item個(gè)數(shù)傳遞進(jìn)來壳快,我們把重心方法重寫布局的方法上:
@interface MyLayout : UICollectionViewFlowLayout
@property(nonatomic,assign)int itemCount;
@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è)置撩笆,以后咱們在討論這個(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è)置节吮。
簡單來說,自定義一個(gè)FlowLayout布局類就是兩個(gè)步驟:
1帚豪、設(shè)計(jì)好我們的布局配置數(shù)據(jù) prepareLayout方法中
2狸臣、返回我們的配置數(shù)組 layoutAttributesForElementsInRect方法中
示例代碼如下:
@implementation MyLayout
{
//這個(gè)數(shù)組就是我們自定義的布局配置數(shù)組
NSMutableArray * _attributeAttay;
}
//數(shù)組的相關(guān)設(shè)置在這個(gè)方法中
//布局前的準(zhǔn)備會(huì)調(diào)用這個(gè)方法
-(void)prepareLayout{
_attributeAttay = [[NSMutableArray alloc]init];
[super prepareLayout];
//演示方便 我們設(shè)置為靜態(tài)的2列
//計(jì)算每一個(gè)item的寬度
float WIDTH = ([UIScreen mainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-self.minimumInteritemSpacing)/2;
//定義數(shù)組保存每一列的高度
//這個(gè)數(shù)組的主要作用是保存每一列的總高度,這樣在布局時(shí),我們可以始終將下一個(gè)Item放在最短的列下面
CGFloat colHight[2]={self.sectionInset.top,self.sectionInset.bottom};
//itemCount是外界傳進(jìn)來的item的個(gè)數(shù) 遍歷來設(shè)置每一個(gè)item的布局
for (int i=0; i<_itemCount; i++) {
//設(shè)置每個(gè)item的位置等相關(guān)屬性
NSIndexPath *index = [NSIndexPath indexPathForItem:i inSection:0];
//創(chuàng)建一個(gè)布局屬性類委粉,通過indexPath來創(chuàng)建
UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index];
//隨機(jī)一個(gè)高度 在40——190之間
CGFloat hight = arc4random()%150+40;
//哪一列高度小 則放到那一列下面
//標(biāo)記最短的列
int width=0;
if (colHight[0]<colHight[1]) {
//將新的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<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return _attributeAttay;
}
@end
自定義完成FlowLayout后,我們在ViewController中進(jìn)行使用:
- (void)viewDidLoad {
[super viewDidLoad];
// 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 = [[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 100;
}
-(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];
return cell;
}
運(yùn)行效果就是我們引言中的截圖忿墅。
三亿柑、UICollectionViewLayoutAttributes類中我們可以配置的屬性
通過上面的例子望薄,我們可以了解,collectionView的item布局其實(shí)是LayoutAttributes類具體配置的采转,這個(gè)類可以配置的布局屬性不止是frame這么簡單,其中還有許多屬性:
//配置item的布局位置
@property (nonatomic) CGRect frame;
//配置item的中心
@property (nonatomic) CGPoint center;
//配置item的尺寸
@property (nonatomic) CGSize size;
//配置item的3D效果
@property (nonatomic) CATransform3D transform3D;
//配置item的bounds
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
//配置item的旋轉(zhuǎn)
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
//配置item的alpha
@property (nonatomic) CGFloat alpha;
//配置item的z坐標(biāo)
@property (nonatomic) NSInteger zIndex; // default is 0
//配置item的隱藏
@property (nonatomic, getter=isHidden) BOOL hidden;
//item的indexpath
@property (nonatomic, strong) NSIndexPath *indexPath;
//獲取item的類型
@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@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)用示例。
二柑营、設(shè)計(jì)一個(gè)圓環(huán)布局
先自定義一個(gè)layout類由境,這個(gè)類繼承于UICollectionViewLayout讥蟆,UICollectionLayout是一個(gè)布局抽象基類瘸彤,我們要使用自定義的布局方式,必須將其子類化玻靡,可能你還記得,我們在進(jìn)行瀑布流布局的時(shí)候使用過UICollectionViewFlowLayout類蝎土,這個(gè)類就是繼承于UICollectionViewLayout類,系統(tǒng)為我們實(shí)現(xiàn)好的一個(gè)布局方案暴构。
@interface MyLayout : UICollectionViewLayout
//這個(gè)int值存儲(chǔ)有多少個(gè)item
@property(nonatomic,assign)int itemCount;
@end
我們需要重寫這個(gè)類的三個(gè)方法取逾,來進(jìn)行圓環(huán)布局的設(shè)置,首先是prepareLayout琉用,為布局做一些準(zhǔn)備工作邑时,使用collectionViewContentSize來設(shè)置內(nèi)容的區(qū)域大小,最后使用layoutAttributesForElementsInRect方法來返回我們的布局信息字典浅浮,這個(gè)和前面瀑布流布局的思路是一樣的:
@implementation MyLayout
{
NSMutableArray * _attributeAttay;
}
-(void)prepareLayout{
[super prepareLayout];
//獲取item的個(gè)數(shù)
_itemCount = (int)[self.collectionView numberOfItemsInSection:0];
_attributeAttay = [[NSMutableArray alloc]init];
//先設(shè)定大圓的半徑 取長和寬最短的
CGFloat radius = MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height)/2;
//計(jì)算圓心位置
CGPoint center = CGPointMake(self.collectionView.frame.size.width/2, self.collectionView.frame.size.height/2);
//設(shè)置每個(gè)item的大小為50*50 則半徑為25
for (int i=0; i<_itemCount; i++) {
UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
//設(shè)置item大小
attris.size = CGSizeMake(50, 50);
//計(jì)算每個(gè)item的圓心位置
/*
.
. .
. . r
. .
.........
*/
//計(jì)算每個(gè)item中心的坐標(biāo)
//算出的x y值還要減去item自身的半徑大小
float x = center.x+cosf(2*M_PI/_itemCount*i)*(radius-25);
float y = center.y+sinf(2*M_PI/_itemCount*i)*(radius-25);
attris.center = CGPointMake(x, y);
[_attributeAttay addObject:attris];
}
}
//設(shè)置內(nèi)容區(qū)域的大小
-(CGSize)collectionViewContentSize{
return self.collectionView.frame.size;
}
//返回設(shè)置數(shù)組
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return _attributeAttay;
}
在viewController中代碼如下:
- (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.layer.masksToBounds = YES;
cell.layer.cornerRadius = 25;
cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1];
return cell;
}
如上非常簡單的一些邏輯控制淮捆,我們就實(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:
@interface MyLayout : UICollectionViewLayout
@end
對(duì)于.m文件的內(nèi)容,前幾篇博客中我們都是在prepareLayout中進(jìn)行布局的靜態(tài)設(shè)置怯晕,那是因?yàn)槲覀兦皫灼┛椭械牟季侄际庆o態(tài)的潜圃,布局并不會(huì)隨著我們的手勢操作而發(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 {
[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;
}
上面我創(chuàng)建了10個(gè)Item,并且在每個(gè)Item上添加了一個(gè)標(biāo)簽烈涮,標(biāo)寫是第幾行朴肺。
在我們自定義的布局類中重寫layoutAttributesForElementsInRect,在其中返回我們的布局?jǐn)?shù)組:
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray * attributes = [[NSMutableArray alloc]init];
//遍歷設(shè)置每個(gè)item的布局屬性
for (int i=0; i<[self.collectionView numberOfItemsInSection:0]; i++) {
[attributes addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]];
}
return attributes;
}
之后坚洽,在我們布局類中重寫layoutAttributesForItemAtIndexPath方法:
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
//創(chuàng)建一個(gè)item布局屬性類
UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//獲取item的個(gè)數(shù)
int itemCounts = (int)[self.collectionView numberOfItemsInSection:0];
//設(shè)置每個(gè)item的大小為260*100
atti.size = CGSizeMake(260, 100);
/*
后邊介紹的代碼添加在這里
*/
return atti;
}
上面的代碼中戈稿,我們什么都沒有做,下面我們一步步來實(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)建空得矩陣
CATransform3D trans3D = CATransform3DIdentity;
//這個(gè)值設(shè)置的是透視度庐舟,影響視覺離投影平面的距離
trans3D.m34 = -1/900.0;
//下面這些屬性 后面會(huì)具體介紹
//這個(gè)是3D滾輪的半徑
CGFloat radius = 50/tanf(M_PI*2/itemCounts/2);
//計(jì)算每個(gè)item應(yīng)該旋轉(zhuǎn)的角度
CGFloat angle = (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)用了一些簡單的幾何和三角函數(shù)的知識(shí)挽牢。如果我們將系統(tǒng)的pickerView沿著y軸旋轉(zhuǎn)90°谱煤,你會(huì)發(fā)現(xiàn)側(cè)面的它是一個(gè)規(guī)則的正多邊形,這里的radius就是這個(gè)多邊形中心到其邊的垂直距離禽拔,也是內(nèi)切圓的半徑刘离,所有的item拼成了一個(gè)正多邊形,示例如下:
通過簡單的數(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è)置舀患,我們再運(yùn)行代碼徽级,效果如下:
仔細(xì)觀察我們可以發(fā)現(xiàn),item以x中軸線進(jìn)行了旋轉(zhuǎn)平均布局构舟,側(cè)面的效果就是我們上面的簡筆畫那樣灰追,下面要進(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)做一些處理。
三、讓滾輪滑動(dòng)起來
通過上面的努力召嘶,我們已經(jīng)靜態(tài)布局出了一個(gè)類似pickerView的滾輪父晶,現(xiàn)在我們再來添加滑動(dòng)滾動(dòng)的效果
首先,我們需要給collectionView一個(gè)滑動(dòng)的范圍弄跌,我們以一屏collectionView的滑動(dòng)距離來當(dāng)做滾輪滾動(dòng)一下的參照甲喝,我們在布局類中的如下方法中返回滑動(dòng)區(qū)域:
-(CGSize)collectionViewContentSize{
return CGSizeMake(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{
return YES;
}
將上面的布局的中心點(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è)置布局的方法中铁蹈,我們在添加一些處理:
//獲取當(dāng)前的偏移量
float offset = self.collectionView.contentOffset.y;
//在角度設(shè)置上宽闲,添加一個(gè)偏移角度
float angleOffset = offset/self.collectionView.frame.size.height;
CGFloat angle = (float)(indexPath.row+angleOffset)/itemCounts*M_PI*2;
再看看效果,沒錯(cuò)握牧,就是這么簡單容诬,滾輪已經(jīng)轉(zhuǎn)了起來。
四沿腰、讓其循環(huán)滾動(dòng)的邏輯
我們再進(jìn)一步览徒,如果滾動(dòng)可以循環(huán),這個(gè)控件將更加炫酷颂龙,添加這樣的邏輯也很簡單习蓬,通過監(jiān)測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);
//大于最后一屏多一屏 放回第一屏
}else if(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{
return CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height*([self.collectionView numberOfItemsInSection:0]+2));
}
//將計(jì)算的具體item角度向前遞推一個(gè)
CGFloat angle = (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篇的博客奄容,從平面上最簡單的規(guī)則擺放的布局,到不規(guī)則的瀑布流布局产徊,再到平面中的圓環(huán)布局昂勒,我們突破了線性布局的局限,在后面囚痴,我們將布局?jǐn)U展到了空間叁怪,在Z軸上進(jìn)行了平移,我們實(shí)現(xiàn)了一個(gè)類似UIPickerView的布局模型深滚,其實(shí)我們還可以再進(jìn)一步奕谭,類比于平面布局,picKerView只是線性排列布局在空間上的旋轉(zhuǎn)與平移痴荐,這次血柳,我們更加充分了利用一下空間的尺寸,來設(shè)計(jì)一個(gè)圓球的布局模型生兆。
二难捌、將布局?jǐn)U展為空間球型
在viewController中先實(shí)現(xiàn)一些準(zhǔn)備代碼:
- (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;
//這里設(shè)置的偏移量是為了無縫進(jìn)行循環(huán)的滾動(dòng)膝宁,具體在上一篇博客中有解釋
collect.contentOffset = CGPointMake(320, 400);
[collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
[self.view addSubview:collect];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
//我們返回30的標(biāo)簽
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 30;
}
-(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, 30, 30)];
label.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];
[cell.contentView addSubview:label];
return cell;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 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);
}else if(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);
}else if(scrollView.contentOffset.x>11*320){
scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x-10*320,scrollView.contentOffset.y);
}
}
這里面的代碼比較上一篇博客中的并沒有什么大的改動(dòng)根吁,只是做了橫坐標(biāo)的兼容员淫。
在我們的layout類中,將代碼修改成如下:
-(void)prepareLayout{
[super prepareLayout];
}
//返回的滾動(dòng)范圍增加了對(duì)x軸的兼容
-(CGSize)collectionViewContentSize{
return CGSizeMake( self.collectionView.frame.size.width*([self.collectionView numberOfItemsInSection:0]+2), self.collectionView.frame.size.height*([self.collectionView numberOfItemsInSection:0]+2));
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * atti = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//獲取item的個(gè)數(shù)
int itemCounts = (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);
CATransform3D trans3D = CATransform3DIdentity;
trans3D.m34 = -1/900.0;
CGFloat radius = 15/tanf(M_PI*2/itemCounts/2);
//根據(jù)偏移量 改變角度
//添加了一個(gè)x的偏移量
float offsety = self.collectionView.contentOffset.y;
float offsetx = self.collectionView.contentOffset.x;
//分別計(jì)算偏移的角度
float angleOffsety = offsety/self.collectionView.frame.size.height;
float angleOffsetx = offsetx/self.collectionView.frame.size.width;
CGFloat angle1 = (float)(indexPath.row+angleOffsety-1)/itemCounts*M_PI*2;
//x击敌,y的默認(rèn)方向相反
CGFloat angle2 = (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);
}else if(indexPath.row%4==2){
trans3D = CATransform3DRotate(trans3D, angle2, 0, 1, 0);
}else if(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;
return atti;
}
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray * attributes = [[NSMutableArray alloc]init];
//遍歷設(shè)置每個(gè)item的布局屬性
for (int i=0; i<[self.collectionView numberOfItemsInSection:0]; i++) {
[attributes addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]];
}
return attributes;
}
布局效果如下:
滑動(dòng)屏幕介返,這個(gè)圓球是可以進(jìn)行滾動(dòng)的。
TIP:這里我們只平均分配了四個(gè)方向上的布局沃斤,如果item更加小也更加多圣蝎,我們可以分配到更多的方向上,使球體更加充實(shí)衡瓶。
轉(zhuǎn)自https://blog.csdn.net/LVXIANGAN/article/details/73826108?utm_source=blogkpcl6