概述
UICollectionView是iOS開發(fā)中最常用的UI控件之一,可以用它來管理一組有序的不同尺寸的視圖威沫,并以可定制的布局來展示它們。UICollectionView支持動(dòng)畫脂凶,當(dāng)視圖被插入敲街,刪除或重新排序時(shí),會(huì)觸發(fā)動(dòng)畫绞旅,動(dòng)畫效果支持自定義摆尝。為了更好的使用UICollectionView,我們有必要對(duì)其進(jìn)行深入了解因悲。
基礎(chǔ)
UICollectionView是由多個(gè)對(duì)象協(xié)作實(shí)現(xiàn)的
集合視圖將視圖的數(shù)據(jù)內(nèi)容與視圖的布局方式分開來管理堕汞。數(shù)據(jù)內(nèi)容由集合視圖的dataSource
對(duì)象管理,而布局方式則是由許多不同的對(duì)象協(xié)作來管理晃琳。下表列出了UIKit中與集合視圖有關(guān)的類讯检,并根據(jù)它們?cè)诩弦晥D中的作用進(jìn)行了劃分。
目的 | 類/協(xié)議 | 描述 |
---|---|---|
頂層容器和管理者 | UICollectionView UICollectionViewController |
UICollectionView定義了顯示視圖內(nèi)容的空間卫旱,它繼承自UIScrollView人灼,能夠根據(jù)內(nèi)容的高度來調(diào)整其滾動(dòng)區(qū)域。其layout布局對(duì)象會(huì)提供布局信息來呈現(xiàn)數(shù)據(jù)顾翼。 UICollectionViewController對(duì)象提供了一個(gè)UICollectionView的視圖控制器級(jí)管理支持投放。 |
內(nèi)容管理 | UICollectionViewDataSource協(xié)議 UICollectionViewDelegate協(xié)議 |
DataSource協(xié)議是必須實(shí)現(xiàn)的,它創(chuàng)造并管理UICollectionView的視圖內(nèi)容暴构。 Delegate協(xié)議能獲取視圖的信息并自定義視圖的行為跪呈,這個(gè)協(xié)議是可選實(shí)現(xiàn)的段磨。 |
內(nèi)容視圖 | UICollectionReusableView UICollectionViewCell |
UICollectionView展示的所有視圖都必須是UICollectionReusableView類的實(shí)例,該類支持回收復(fù)用機(jī)制耗绿。在視圖滾動(dòng)時(shí),回收復(fù)用視圖而不是重新創(chuàng)建苹支,能極大提高性能。 UICollectionViewCell對(duì)象是用來展示主要數(shù)據(jù)的可重用視圖,該類繼承自UICollectionReusableView误阻。 |
布局 | UICollectionViewLayout UICollectionViewLayoutAttributes UICollectionViewUpdateItem |
UICollectionViewLayout的子類被稱為布局對(duì)象债蜜,它負(fù)責(zé)定義集合視圖中的cell和可重用視圖的位置,大小究反,視覺效果寻定。在布局過程中,布局對(duì)象UICollectionViewLayout會(huì)創(chuàng)建一個(gè)布局屬性對(duì)象UICollectionViewLayoutAttributes去告訴集合視圖在什么位置精耐,用什么樣視覺外觀去展示cell和可重用視圖狼速。當(dāng)在集合視圖中插入、刪除卦停、移動(dòng)數(shù)據(jù)項(xiàng)時(shí)向胡,布局對(duì)象會(huì)接收到UICollectionViewUpdateItem類的實(shí)例,不需要自行創(chuàng)建該類的實(shí)例惊完。 |
流水布局 | UICollectionViewFlowLayout協(xié)議 UICollectionViewDelegateFlowLayout協(xié)議 |
UICollectionViewFlowLayout類是用于實(shí)現(xiàn)網(wǎng)格或其他基于行的布局的具體布局對(duì)象僵芹。 可以按照原樣使用該類或者配合UICollectionViewDelegateFlowLayout協(xié)議一起使用,這樣就可以動(dòng)態(tài)自定義布局信息小槐。 |
集合視圖從其dataSource
對(duì)象中獲取要展示的cell的數(shù)據(jù)內(nèi)容拇派,并通過其delegate
對(duì)象去管理cell的選中和高亮等狀態(tài)。布局對(duì)象負(fù)責(zé)決定cell所在的位置凿跳,布局屬性對(duì)象記錄了cell的布局屬性件豌,布局對(duì)象將布局屬性對(duì)象傳遞給集合視圖,集合視圖接收到布局屬性信息后創(chuàng)建并展示cell控嗜。
重用視圖提高性能
集合視圖通過復(fù)用已被回收的cell來提高效率苟径,當(dāng)cell滾動(dòng)到屏幕外時(shí),它們不會(huì)被銷毀躬审,但會(huì)被移出容器視圖并放置到重用隊(duì)列中棘街。當(dāng)有新的內(nèi)容將要滾動(dòng)到屏幕中時(shí),如果重用隊(duì)列中有可復(fù)用的cell承边,會(huì)首先從重用隊(duì)列中取遭殉,并重置被取出來的cell的數(shù)據(jù),然后將其添加到容器視圖中展示博助。如果重用隊(duì)列沒有可復(fù)用的cell险污,這時(shí)才會(huì)新創(chuàng)建一個(gè)cell去展示。為了方便這種循環(huán),集合視圖中展示的視圖類都必須繼承自UICollectionReusableView
類蛔糯。
集合視圖支持三種不同類型的可重用視圖拯腮,每種視圖都具有特定的用途:
- cell(單元格)展示集合視圖的主要內(nèi)容,每個(gè)cell展示的內(nèi)容由
dataSource
對(duì)象提供蚁飒。每個(gè)cell都必須是UICollectionViewCell
的實(shí)例动壤,同時(shí)也可以根據(jù)需要對(duì)其子類化。cell對(duì)象支持管理其選中和高亮狀態(tài)淮逻。 - supplementary view(補(bǔ)充視圖)展示每個(gè)section(分區(qū))的信息琼懊。和cell相同的是:supplementary view也是數(shù)據(jù)驅(qū)動(dòng)的。不同的是:supplementary view是可選的而不是強(qiáng)制的爬早。supplementary view的使用和布局是由布局對(duì)象管理的哼丈,系統(tǒng)提供的流水布局支持設(shè)置header和footer作為可選的supplementary view。
- decoration view(裝飾視圖)與
dataSource
對(duì)象提供的數(shù)據(jù)不相關(guān)筛严,完全屬于布局對(duì)象醉旦。布局對(duì)象可能會(huì)使用它自定義集合視圖背景。
布局對(duì)象控制視圖的視覺效果
布局對(duì)象負(fù)責(zé)確定集合視圖中每個(gè)cell的位置和視覺樣式桨啃。雖然dataSource
對(duì)象提供了要展示的視圖和實(shí)際內(nèi)容髓抑,但布局對(duì)象確定了這些視圖的位置,大小以及其他與外觀相關(guān)的屬性优幸。這種責(zé)任劃分使得我們能夠在動(dòng)態(tài)的更改布局時(shí)無需更改dataSource
對(duì)象提供的數(shù)據(jù)。
布局對(duì)象并不擁有任何視圖褪猛,它只會(huì)生成用來描述cell网杆,supplementary view和decoration view的位置,大小伊滋,視覺樣式的布局屬性碳却,并將布局屬性傳遞給集合視圖,集合視圖將這些屬性應(yīng)用于實(shí)際的視圖對(duì)象笑旺。
布局對(duì)象可以隨意生成視圖的位置昼浦,大小以及視覺樣式屬性,沒有任何限制筒主。只有布局對(duì)象能改變視圖在集合視圖中的位置关噪,它能移動(dòng)視圖,也能隨機(jī)切換橫豎屏乌妙,甚至能復(fù)位某視圖而不用考慮此視圖周圍的視圖使兔。例如,如果有需要藤韵,布局對(duì)象可以將所有視圖疊加在一起虐沥。
下圖顯示了垂直滾動(dòng)的流水布局對(duì)象如何布置cell。在垂直滾動(dòng)流水布局中,內(nèi)容區(qū)域的寬度保持固定欲险,高度隨著內(nèi)容高度的增加而增加镐依。布局對(duì)象一次只放置一個(gè)cell,在放置前會(huì)先計(jì)算出cell在容器視圖中的frame天试,為cell選擇最合適的位置槐壳。
使用
必須為集合視圖提供一個(gè)dataSource
對(duì)象,集合視圖從dataSource
對(duì)象中獲取要顯示的內(nèi)容秋秤。它可以是一個(gè)數(shù)據(jù)模型對(duì)象宏粤,也可以是管理集合視圖的視圖控制器,對(duì)dataSource
對(duì)象的唯一要求是它必須能夠提供集合視圖所需的所有信息灼卢。delegate
對(duì)象是可選提供的绍哎,其被用于管理與內(nèi)容的呈現(xiàn)以及交互有關(guān)的方面。它的主要職責(zé)是管理cell的選中和高亮狀態(tài)鞋真,也可以擴(kuò)展UICollectionViewDelegate
協(xié)議以提供其他信息崇堰。流水布局對(duì)象就擴(kuò)展了UICollectionViewDelegate
協(xié)議來定制布局,例如涩咖,cell的大小和它們之間的間距海诲。
UICollectionViewDataSource
提供集合視圖包含的section(分區(qū))數(shù)量:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView
{
return [_dataArray count];
}
提供每個(gè)section包含的item(單元格)數(shù)量:
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
{
NSArray* sectionArray = [_dataArray objectAtIndex:section];
return [sectionArray count];
}
根據(jù)IndexPath提供對(duì)應(yīng)的cell:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"reuseIdentifier" forIndexPath:indexPath];
return cell;
}
根據(jù)IndexPath提供對(duì)應(yīng)的supplementary view,流水布局的supplementary view分為Header和Footer兩種類型:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
// UICollectionElementKindSectionHeader返回Header檩互,UICollectionElementKindSectionFooter返回Footer
if ([kind isEqualToString:UICollectionElementKindSectionHeader])
{
UICollectionReusableView *supplementaryView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"HeaderReuseIdentifier" forIndexPath:indexPath];
return supplementaryView;
}else
{
UICollectionReusableView *supplementaryView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"FooterReuseIdentifier" forIndexPath:indexPath];
return supplementaryView;
}
}
注意:當(dāng)集合視圖展示的cell數(shù)量較少時(shí)特幔,集合視圖的
bounce
屬性會(huì)默認(rèn)關(guān)閉,而有時(shí)候我們的頁面需要下拉刷新數(shù)據(jù)的功能闸昨,這時(shí)只需要設(shè)置alwaysBounceVertical
屬性設(shè)為YES
即可蚯斯。
UICollectionViewDelegate
設(shè)置cell是否能被選中:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
當(dāng)集合視圖的allowsMultipleSelection
多選屬性為YES
時(shí),設(shè)置是否可以點(diǎn)擊取消選中已被選中的cell:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
已選中cell后回調(diào):
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// 執(zhí)行已選中后所需要的操作
}
已取消選中cell后回調(diào):
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
// 執(zhí)行已取消選中后所需要的操作
}
設(shè)置cell被選中時(shí)是否支持高亮:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
選中cell時(shí)觸發(fā)高亮后回調(diào)饵较,可以在這里改變cell的背景色:
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor lightGrayColor];
}
cell被取消選中變?yōu)槠胀顟B(tài)后回調(diào)拍嵌,可以在這里還原cell的背景色:
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor whiteColor];
}
注意:點(diǎn)擊cell時(shí),cell的狀態(tài)變化過程為:手指接觸屏幕時(shí)循诉,cell狀態(tài)變?yōu)楦吡梁崃荆藭r(shí)cell還未被選中。當(dāng)手指離開屏幕后茄猫,cell狀態(tài)變回到普通狀態(tài)狈蚤,然后cell被集合視圖選中。當(dāng)快速點(diǎn)擊選中cell時(shí)划纽,由于狀態(tài)變化很快炫惩,導(dǎo)致人眼看不出來cell背景色有發(fā)生變化,實(shí)際上是發(fā)生了變化的阿浓。而長(zhǎng)按選中cell時(shí)他嚷,可以看到背景色的變化。
UICollectionViewDelegateFlowLayout
該協(xié)議是對(duì)UICollectionViewDelegate
的擴(kuò)展,能夠動(dòng)態(tài)返回cell的大小筋蓖,和cell之間的最小間距等卸耘。
根據(jù)IndexPath返回對(duì)應(yīng)的Cell的大小:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(80.0, 80.0);
}
返回cell到所在section的四周邊界的距離:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
}
根據(jù)Section返回對(duì)應(yīng)的cell之間的行最小間距:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 10.0;
}
根據(jù)section返回對(duì)應(yīng)的cell之間的列最小間距:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 10.0;
}
根據(jù)section返回對(duì)應(yīng)的Header大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeMake(collectionView.frame.size.width, 40.0);
}
根據(jù)section返回對(duì)應(yīng)的Footer大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return CGSizeMake(collectionView.frame.size.width, 40.0);
}
cell和supplementary view的重用
視圖的重用避免了不斷生成和銷毀對(duì)象的操作粘咖,提高了程序運(yùn)行的效率蚣抗。要想重用cell和supplementary view,首先需要注冊(cè)cell和supplementary view瓮下,有種三種注冊(cè)方式:
- 使用storyboard布局時(shí)翰铡,直接拖拽cell或者supplementary view到storyboard中,設(shè)置好重用標(biāo)識(shí)即可讽坏。
- 使用xib布局時(shí)锭魔,設(shè)置重用標(biāo)識(shí)后,使用
registerNib:forCellWithReuseIdentifier:
方法來注冊(cè)cell路呜,使用registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
方法來注冊(cè)supplementary view迷捧。 - 使用代碼布局時(shí),使用
registerClass:forCellWithReuseIdentifier:
方法來注冊(cè)cell胀葱,使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
方法來注冊(cè)supplementary view漠秋。
注意:使用純代碼自定義cell和supplementary view時(shí),需要重寫
initWithFrame:
方法抵屿,init
方法不會(huì)被調(diào)用庆锦。
dataSource
對(duì)象為集合視圖配置cell和supplementary view時(shí),使用dequeueReusableCellWithReuseIdentifier:forIndexPath:
方法直接從重用隊(duì)列中取cell轧葛,使用dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
方法直接從重用隊(duì)列中取supplementary view搂抒。當(dāng)重用隊(duì)列中沒有可復(fù)用的視圖時(shí),會(huì)自動(dòng)幫我們新創(chuàng)建一個(gè)可用的視圖朝群。
cell的插入,刪除和移動(dòng)
插入中符,刪除姜胖,移動(dòng)單個(gè)cell或者某個(gè)section的所有cell時(shí),遵循下面兩個(gè)步驟:
- 更新數(shù)據(jù)源對(duì)象中的數(shù)據(jù)內(nèi)容淀散。
- 調(diào)用對(duì)應(yīng)的插入右莱,刪除或者移動(dòng)方法。
集合視圖插入档插,刪除和移動(dòng)cell之前慢蜓,必須先對(duì)應(yīng)更新數(shù)據(jù)源。如果數(shù)據(jù)源沒有更新郭膛,程序運(yùn)行就會(huì)崩潰晨抡。當(dāng)插入,刪除或者移動(dòng)cell時(shí),會(huì)自動(dòng)添加動(dòng)畫效果來反映集合視圖的更改耘柱。在執(zhí)行動(dòng)畫時(shí)如捅,如果還需要同步
執(zhí)行其他操作,可以使用performBatchUpdates:completion:
方法调煎,在updates block
內(nèi)執(zhí)行所有插入镜遣,刪除或移動(dòng)調(diào)用,動(dòng)畫執(zhí)行完畢后會(huì)調(diào)用completion block
士袄。
[self.collectionView performBatchUpdates:^{
// 執(zhí)行更改操作
} completion:^(BOOL finished){
if (finished)
{
// 執(zhí)行其他操作
}
}];
長(zhǎng)按cell彈出編輯菜單
長(zhǎng)按某個(gè)cell時(shí)悲关,可以彈出一個(gè)編輯菜單,能夠用于剪切娄柳,粘貼寓辱,復(fù)制這個(gè)cell。長(zhǎng)按彈出編輯菜單西土,delegate
對(duì)象必須實(shí)現(xiàn)下面3個(gè)委托方法:
是否顯示編輯菜單:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
可以執(zhí)行哪些操作:
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
{
if ([NSStringFromSelector(action) isEqualToString:@"copy:"]|| [NSStringFromSelector(action) isEqualToString:@"paste:"])
{
return YES;
}
return NO;
}
點(diǎn)擊菜單中選項(xiàng)后回調(diào):
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
{
if ([NSStringFromSelector(action) isEqualToString:@"cut:"])
{
// 剪切操作
}else if ([NSStringFromSelector(action) isEqualToString:@"copy:"])
{
// 復(fù)制操作
}else if ([NSStringFromSelector(action) isEqualToString:@"paste:"])
{
// 粘貼操作
}
}
集合視圖只支持cut:
讶舰,copy:
,paste:
三種編輯操作需了。想要了解如何配合剪貼板使用這些操作跳昼,可以參看Text Programming Guide for iOS。
切換布局時(shí)的轉(zhuǎn)場(chǎng)動(dòng)畫
切換布局最簡(jiǎn)單的方式是使用setCollectionViewLayout:animated:
方法肋乍。在UICollectionViewController
之間跳轉(zhuǎn)時(shí)鹅颊,如果需要交互式轉(zhuǎn)場(chǎng)切換布局或者控制切換過程,可以使用UICollectionViewTransitionLayout
對(duì)象墓造。
UICollectionViewTransitionLayout
類是一種特殊的布局類堪伍,它繼承自UICollectionViewLayout
類,在切換到新布局的過程中,它將作為集合視圖的臨時(shí)布局觅闽。使用UICollectionViewTransitionLayout
布局對(duì)象時(shí)帝雇,可以使用不同的計(jì)時(shí)算法讓動(dòng)畫遵循非線性路徑,或者根據(jù)傳入的觸摸事件進(jìn)行移動(dòng)蛉拙。官方提供的UICollectionViewTransitionLayout
類支持對(duì)新布局的線性轉(zhuǎn)換尸闸,但我們可以對(duì)其進(jìn)行子類化來實(shí)現(xiàn)任何所需的效果。
UICollectionViewLayout
提供了幾種跟蹤布局之間轉(zhuǎn)換進(jìn)度的方法孕锄,UICollectionViewTransitionLayout
類通過transitionProgress
屬性來跟蹤轉(zhuǎn)場(chǎng)切換的進(jìn)度吮廉,當(dāng)轉(zhuǎn)場(chǎng)切換開始后,需要定期更新此屬性值來指示完成的百分比畸肆。使用自定義UICollectionViewTransitionLayout
對(duì)象時(shí)宦芦,UICollectionViewTransitionLayout
類提供來2種跟蹤與布局相關(guān)的值的方法:updateValue:forAnimatedKey:
和valueForAnimatedKey:
。
轉(zhuǎn)場(chǎng)切換布局時(shí)轴脐,使用UICollectionViewTransitionLayout
對(duì)象的步驟如下:
- 使用
initWithCurrentLayout:nextLayout:
方法創(chuàng)建一個(gè)UICollectionViewTransitionLayout
實(shí)例對(duì)象调卑。 - 定期修改
transitionProgress
屬性值來指示轉(zhuǎn)場(chǎng)切換的進(jìn)度抡砂。在修改轉(zhuǎn)場(chǎng)進(jìn)度后,一定要調(diào)用invalidateLayout
方法來廢棄當(dāng)前布局并更新布局令野。 - 集合視圖的
delegate
對(duì)象實(shí)現(xiàn)委托方法collectionView:transitionLayoutForOldLayout:newLayout:
返回創(chuàng)建的UICollectionViewTransitionLayout
實(shí)例對(duì)象舀患。 - 可以使用
updateValue:forAnimatedKey:
方法來修改與布局相關(guān)的值。
進(jìn)階
流水布局
官方提供的UICollectionViewFlowLayout
流水布局對(duì)象實(shí)現(xiàn)了基于行的斷開布局气破,單元格被放置在線性路徑上聊浅,并沿著該行放置盡可能多的單元格,當(dāng)前行上的空間在使用最小間距也不足以放置下一個(gè)單元格時(shí)现使,會(huì)重新計(jì)算出合適的當(dāng)前行上擺放的單元格之間的間距低匙,如果該行上只有一個(gè)單元格,那么它會(huì)被置中碳锈,然后會(huì)創(chuàng)建新的一行并在該行重復(fù)之前的布局過程顽冶。
使用時(shí),通過固定單元格的大小和單元格之間的最小間距來實(shí)現(xiàn)網(wǎng)格狀視圖售碳,同時(shí)也可以任意設(shè)置單元格的大小和單元格之間的間距來實(shí)現(xiàn)不規(guī)則排列的視圖强重。當(dāng)單元格的大小,單元格之間的最小間距贸人,單元格到所在分區(qū)四周的邊距以及Header和Footer的大小固定時(shí)间景,可以直接設(shè)置itemSize
,minimumLineSpacing
艺智,minimumInteritemSpacing
倘要,sectionInset
,headerReferenceSize
十拣,footerReferenceSize
屬性值封拧。如果想要?jiǎng)討B(tài)設(shè)置它們,需要集合視圖的delegate
對(duì)象實(shí)現(xiàn)UICollectionViewDelegateFlowLayout
協(xié)議的委托方法夭问。
自定義布局
理解布局過程
子類化UICollectionViewLayout
實(shí)現(xiàn)自定義布局有兩個(gè)關(guān)鍵任務(wù)需要完成:
- 指定可滾動(dòng)內(nèi)容區(qū)域的大小泽西。
- 為每個(gè)單元格和補(bǔ)充視圖提供布局屬性對(duì)象以便集合視圖定位。
集合視圖和自定義布局對(duì)象一起工作來管理整體布局過程缰趋,當(dāng)集合視圖需要用到布局信息時(shí)捧杉,它會(huì)請(qǐng)求布局對(duì)象提供這些布局信息。調(diào)用布局對(duì)象的invalidateLayout
方法會(huì)告知集合視圖顯式更新其布局埠胖,此方法會(huì)廢棄現(xiàn)有的布局屬性糠溜,并強(qiáng)制布局對(duì)象生成新的布局屬性淳玩。
不要將布局對(duì)象的invalidateLayout
方法與集合視圖的reloadData
方法混淆直撤,調(diào)用invalidateLayout
方法不一定會(huì)移除當(dāng)前現(xiàn)有的單元格和子視圖,它只會(huì)強(qiáng)制布局對(duì)象重新計(jì)算移動(dòng)蜕着、添加或刪除單元格時(shí)所需的所有布局信息谋竖。如果數(shù)據(jù)源對(duì)象提供的數(shù)據(jù)發(fā)生了更改红柱,則應(yīng)該調(diào)用reloadData
方法。使用這兩種方法來更新布局時(shí)蓖乘,實(shí)際的布局過程都是一樣的锤悄。
在布局過程中,集合視圖會(huì)始終按順序來調(diào)用布局對(duì)象的以下三種方法:
- (void)prepareLayout
- (CGSize)collectionViewContentSize
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
集合視圖調(diào)用布局對(duì)象的prepareLayout
方法嘉抒,提供機(jī)會(huì)讓我們提前計(jì)算確定布局屬性信息時(shí)所需的數(shù)據(jù)零聚,從計(jì)算出來的數(shù)據(jù)中要能夠得知集合視圖整個(gè)內(nèi)容區(qū)域的大小。
集合視圖調(diào)用布局對(duì)象的collectionViewContentSize
方法獲得內(nèi)容大小來適當(dāng)?shù)呐渲闷錆L動(dòng)視圖些侍,在這里根據(jù)提前計(jì)算的數(shù)據(jù)返回整個(gè)內(nèi)容區(qū)域的大小隶症。如果內(nèi)容大小在垂直和水平方向上都超出當(dāng)前設(shè)備屏幕的邊界,則會(huì)允許滾動(dòng)視圖同時(shí)在這兩個(gè)方向上滾動(dòng)岗宣,而UICollectionViewFlowLayout
只能在一個(gè)方向上滾動(dòng)蚂会。
集合視圖會(huì)基于當(dāng)前的滾動(dòng)位置調(diào)用layoutAttributesForElementsInRect:
方法來查找在特定區(qū)域中的單元格和視圖的布局屬性,此區(qū)域和可視區(qū)域可能相同也可能不同耗式,在這里遍歷提前生成的所有的布局屬性信息胁住,檢查每個(gè)布局信息的frame,返回所有frame和給定rect相交的布局屬性刊咳,這樣核心布局過程就完成了彪见。
可以在prepareLayout
方法中生成布局屬性對(duì)象后緩存起來,也可以在layoutAttributesForElementsInRect:
方法中生成布局屬性對(duì)象芦缰,但是集合視圖在滾動(dòng)過程中會(huì)多次調(diào)用layoutAttributesForElementsInRect:
方法企巢,這樣就會(huì)為視圖重復(fù)計(jì)算布局屬性,會(huì)有性能損耗让蕾。
布局完成后浪规,單元格和視圖的布局屬性會(huì)保持不變。調(diào)用布局對(duì)象的invalidateLayout
會(huì)廢棄當(dāng)前所有布局信息探孝,然后再次從調(diào)用prepareLayout
方法開始笋婿,重復(fù)布局過程生成新的布局信息。集合視圖在滾動(dòng)過程中顿颅,會(huì)不斷調(diào)用布局對(duì)象的shouldInvalidateLayoutForBoundsChange:
方法來判斷是否需要廢棄當(dāng)前布局并重新生成布局缸濒。當(dāng)集合視圖的bounds
屬性發(fā)生變化時(shí),也會(huì)調(diào)用shouldInvalidateLayoutForBoundsChange:
方法粱腻。
調(diào)用invalidateLayout
方法后不會(huì)立即開始布局更新過程庇配,該方法僅將布局標(biāo)記為與數(shù)據(jù)不一致并需要更新。在下一個(gè)視圖更新周期中绍些,集合視圖會(huì)檢查其布局是否為臟捞慌,如果是,則更新布局柬批。也就是說啸澡,當(dāng)我們快速連續(xù)地調(diào)用invalidateLayout
方法多次后袖订,不會(huì)每次調(diào)用都立即更新布局。
創(chuàng)建布局信息對(duì)象
官方提供了三種方法來創(chuàng)建UICollectionViewLayoutAttributes
布局信息對(duì)象:
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath
要根據(jù)視圖的類型調(diào)用對(duì)應(yīng)的方法來生成布局屬性對(duì)象嗅虏,因?yàn)榧弦晥D會(huì)根據(jù)布局信息對(duì)象的representedElementCategory
屬性從數(shù)據(jù)源對(duì)象中獲取對(duì)應(yīng)類型的視圖洛姑,使用錯(cuò)誤的方法生成布局信息對(duì)象會(huì)導(dǎo)致集合視圖在錯(cuò)誤的位置創(chuàng)建錯(cuò)誤的視圖。
生成布局屬性對(duì)象后皮服,一定要根據(jù)前面提前計(jì)算的數(shù)據(jù)設(shè)置好frame
或者center
和size
屬性楞艾,使集合視圖能夠確定對(duì)應(yīng)的視圖的位置和大小。同時(shí)龄广,還可以設(shè)置transform
产徊,alpha
,hidden
等屬性來控制對(duì)應(yīng)視圖的視覺效果蜀细。如果視圖的布局是重疊的舟铜,則可以設(shè)置zIndex
屬性值來確保視圖的順序一致。如果官方提供UICollectionViewLayoutAttributes
標(biāo)準(zhǔn)類無法滿足需求奠衔,可以對(duì)其子類化并擴(kuò)展谆刨,以存儲(chǔ)和視圖外觀有關(guān)的信息。當(dāng)對(duì)布局屬性進(jìn)行子類化時(shí)归斤,需要實(shí)現(xiàn)用于比較自定義屬性的isEqual:
方法痊夭,因?yàn)榧弦晥D對(duì)其某些操作使用此方法。
根據(jù)需要為單個(gè)視圖提供布局屬性
布局對(duì)象還需要能夠根據(jù)需要為單個(gè)視圖提供布局屬性脏里,因?yàn)榧弦晥D會(huì)在執(zhí)行Cell的插入她我,刪除,移動(dòng)和刷新動(dòng)畫時(shí)請(qǐng)求該布局信息迫横。需要覆寫下面三種方法:
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath
在三種方法中番舆,需要返回已計(jì)算好的對(duì)應(yīng)視圖的布局屬性信息,返回屬性時(shí)矾踱,不應(yīng)更改布局屬性恨狈。如果布局中不包含任何補(bǔ)充視圖和裝飾視圖,則不需要覆寫后兩種方法呛讲。
自定義cell的插入禾怠、刪除、移動(dòng)和刷新動(dòng)畫
集合視圖調(diào)用對(duì)應(yīng)的方法插入贝搁、刪除吗氏、刷新、移動(dòng)cell時(shí)雷逆,布局對(duì)象會(huì)調(diào)用invalidateLayout
方法廢棄現(xiàn)有的布局信息弦讽,重新執(zhí)行前面提到的布局過程生成新的布局屬性。在集合視圖更新前調(diào)用prepareForCollectionViewUpdates:
方法告知要更新的cell在更新前的indexPath
和更新完成后的indexPath
关面,以及其要執(zhí)行的更新方式坦袍,需要重寫此方法記錄這些indexPath
。
之后等太,集合視圖會(huì)執(zhí)行兩個(gè)動(dòng)畫:更新布局前每個(gè)cell被移除的動(dòng)畫和更新布局后每個(gè)cell顯示的動(dòng)畫捂齐,我們看到的動(dòng)畫效果是由這兩個(gè)動(dòng)畫組合而成的。在執(zhí)行動(dòng)畫過程中缩抡,布局對(duì)象會(huì)調(diào)用finalLayoutAttributesForDisappearingItemAtIndexPath:
方法獲取對(duì)應(yīng)indexPath
的cell被移除時(shí)的最終布局屬性來執(zhí)行動(dòng)畫:更新布局前的布局屬性值-->cell被移除時(shí)的最終布局屬性值奠宜,調(diào)用initialLayoutAttributesForAppearingItemAtIndexPath:
方法獲取對(duì)應(yīng)indexPath
的cell顯示時(shí)的起始布局屬性來執(zhí)行動(dòng)畫:cell顯示時(shí)的起始布局屬性值-->更新布局后的cell布局屬性值。
插入瞻想、刪除压真、移動(dòng)cell時(shí),會(huì)導(dǎo)致其周圍cell的布局屬性發(fā)生變化蘑险,這些cell會(huì)強(qiáng)制執(zhí)行這個(gè)動(dòng)畫:cell更新布局前的frame-->cell更新布局后的frame滴肿,這是官方在內(nèi)部實(shí)現(xiàn)的。在重寫finalLayoutAttributesForDisappearingItemAtIndexPath:
和initialLayoutAttributesForAppearingItemAtIndexPath:
方法設(shè)置執(zhí)行動(dòng)畫用到的布局屬性時(shí)佃迄,最好檢查一下傳入的indexPath
與調(diào)用prepareForCollectionViewUpdates:
方法時(shí)記錄的indexPath
是否一致泼差。