概述
UICollectionView是iOS開發中最常用的UI控件之一,可以用它來管理一組有序的不同尺寸的視圖,并以可定制的布局來展示它們。UICollectionView支持動畫,當視圖被插入,刪除或重新排序時,會觸發動畫,動畫效果支持自定義。為了更好的使用UICollectionView,我們有必要對其進行深入了解。
基礎
UICollectionView是由多個對象協作實現的
集合視圖將視圖的數據內容與視圖的布局方式分開來管理。數據內容由集合視圖的dataSource
對象管理,而布局方式則是由許多不同的對象協作來管理。下表列出了UIKit中與集合視圖有關的類,并根據它們在集合視圖中的作用進行了劃分。
目的 | 類/協議 | 描述 |
---|---|---|
頂層容器和管理者 | UICollectionView UICollectionViewController |
UICollectionView定義了顯示視圖內容的空間,它繼承自UIScrollView,能夠根據內容的高度來調整其滾動區域。其layout布局對象會提供布局信息來呈現數據。 UICollectionViewController對象提供了一個UICollectionView的視圖控制器級管理支持。 |
內容管理 | UICollectionViewDataSource協議 UICollectionViewDelegate協議 |
DataSource協議是必須實現的,它創造并管理UICollectionView的視圖內容。 Delegate協議能獲取視圖的信息并自定義視圖的行為,這個協議是可選實現的。 |
內容視圖 | UICollectionReusableView UICollectionViewCell |
UICollectionView展示的所有視圖都必須是UICollectionReusableView類的實例,該類支持回收復用機制。在視圖滾動時,回收復用視圖而不是重新創建,能極大提高性能。 UICollectionViewCell對象是用來展示主要數據的可重用視圖,該類繼承自UICollectionReusableView。 |
布局 | UICollectionViewLayout UICollectionViewLayoutAttributes UICollectionViewUpdateItem |
UICollectionViewLayout的子類被稱為布局對象,它負責定義集合視圖中的cell和可重用視圖的位置,大小,視覺效果。在布局過程中,布局對象UICollectionViewLayout會創建一個布局屬性對象UICollectionViewLayoutAttributes去告訴集合視圖在什么位置,用什么樣視覺外觀去展示cell和可重用視圖。當在集合視圖中插入、刪除、移動數據項時,布局對象會接收到UICollectionViewUpdateItem類的實例,不需要自行創建該類的實例。 |
流水布局 | UICollectionViewFlowLayout協議 UICollectionViewDelegateFlowLayout協議 |
UICollectionViewFlowLayout類是用于實現網格或其他基于行的布局的具體布局對象。 可以按照原樣使用該類或者配合UICollectionViewDelegateFlowLayout協議一起使用,這樣就可以動態自定義布局信息。 |
集合視圖從其dataSource
對象中獲取要展示的cell的數據內容,并通過其delegate
對象去管理cell的選中和高亮等狀態。布局對象負責決定cell所在的位置,布局屬性對象記錄了cell的布局屬性,布局對象將布局屬性對象傳遞給集合視圖,集合視圖接收到布局屬性信息后創建并展示cell。
重用視圖提高性能
集合視圖通過復用已被回收的cell來提高效率,當cell滾動到屏幕外時,它們不會被銷毀,但會被移出容器視圖并放置到重用隊列中。當有新的內容將要滾動到屏幕中時,如果重用隊列中有可復用的cell,會首先從重用隊列中取,并重置被取出來的cell的數據,然后將其添加到容器視圖中展示。如果重用隊列沒有可復用的cell,這時才會新創建一個cell去展示。為了方便這種循環,集合視圖中展示的視圖類都必須繼承自UICollectionReusableView
類。
集合視圖支持三種不同類型的可重用視圖,每種視圖都具有特定的用途:
- cell(單元格)展示集合視圖的主要內容,每個cell展示的內容由
dataSource
對象提供。每個cell都必須是UICollectionViewCell
的實例,同時也可以根據需要對其子類化。cell對象支持管理其選中和高亮狀態。 - supplementary view(補充視圖)展示每個section(分區)的信息。和cell相同的是:supplementary view也是數據驅動的。不同的是:supplementary view是可選的而不是強制的。supplementary view的使用和布局是由布局對象管理的,系統提供的流水布局支持設置header和footer作為可選的supplementary view。
- decoration view(裝飾視圖)與
dataSource
對象提供的數據不相關,完全屬于布局對象。布局對象可能會使用它自定義集合視圖背景。
布局對象控制視圖的視覺效果
布局對象負責確定集合視圖中每個cell的位置和視覺樣式。雖然dataSource
對象提供了要展示的視圖和實際內容,但布局對象確定了這些視圖的位置,大小以及其他與外觀相關的屬性。這種責任劃分使得我們能夠在動態的更改布局時無需更改dataSource
對象提供的數據。
布局對象并不擁有任何視圖,它只會生成用來描述cell,supplementary view和decoration view的位置,大小,視覺樣式的布局屬性,并將布局屬性傳遞給集合視圖,集合視圖將這些屬性應用于實際的視圖對象。
布局對象可以隨意生成視圖的位置,大小以及視覺樣式屬性,沒有任何限制。只有布局對象能改變視圖在集合視圖中的位置,它能移動視圖,也能隨機切換橫豎屏,甚至能復位某視圖而不用考慮此視圖周圍的視圖。例如,如果有需要,布局對象可以將所有視圖疊加在一起。
下圖顯示了垂直滾動的流水布局對象如何布置cell。在垂直滾動流水布局中,內容區域的寬度保持固定,高度隨著內容高度的增加而增加。布局對象一次只放置一個cell,在放置前會先計算出cell在容器視圖中的frame,為cell選擇最合適的位置。
使用
必須為集合視圖提供一個dataSource
對象,集合視圖從dataSource
對象中獲取要顯示的內容。它可以是一個數據模型對象,也可以是管理集合視圖的視圖控制器,對dataSource
對象的唯一要求是它必須能夠提供集合視圖所需的所有信息。delegate
對象是可選提供的,其被用于管理與內容的呈現以及交互有關的方面。它的主要職責是管理cell的選中和高亮狀態,也可以擴展UICollectionViewDelegate
協議以提供其他信息。流水布局對象就擴展了UICollectionViewDelegate
協議來定制布局,例如,cell的大小和它們之間的間距。
UICollectionViewDataSource
提供集合視圖包含的section(分區)數量:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView
{
return [_dataArray count];
}
提供每個section包含的item(單元格)數量:
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
{
NSArray* sectionArray = [_dataArray objectAtIndex:section];
return [sectionArray count];
}
根據IndexPath提供對應的cell:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"reuseIdentifier" forIndexPath:indexPath];
return cell;
}
根據IndexPath提供對應的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;
}
}
注意:當集合視圖展示的cell數量較少時,集合視圖的
bounce
屬性會默認關閉,而有時候我們的頁面需要下拉刷新數據的功能,這時只需要設置alwaysBounceVertical
屬性設為YES
即可。
UICollectionViewDelegate
設置cell是否能被選中:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
當集合視圖的allowsMultipleSelection
多選屬性為YES
時,設置是否可以點擊取消選中已被選中的cell:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
已選中cell后回調:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// 執行已選中后所需要的操作
}
已取消選中cell后回調:
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
// 執行已取消選中后所需要的操作
}
設置cell被選中時是否支持高亮:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
選中cell時觸發高亮后回調,可以在這里改變cell的背景色:
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor lightGrayColor];
}
cell被取消選中變為普通狀態后回調,可以在這里還原cell的背景色:
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor whiteColor];
}
注意:點擊cell時,cell的狀態變化過程為:手指接觸屏幕時,cell狀態變為高亮,此時cell還未被選中。當手指離開屏幕后,cell狀態變回到普通狀態,然后cell被集合視圖選中。當快速點擊選中cell時,由于狀態變化很快,導致人眼看不出來cell背景色有發生變化,實際上是發生了變化的。而長按選中cell時,可以看到背景色的變化。
UICollectionViewDelegateFlowLayout
該協議是對UICollectionViewDelegate
的擴展,能夠動態返回cell的大小,和cell之間的最小間距等。
根據IndexPath返回對應的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);
}
根據Section返回對應的cell之間的行最小間距:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 10.0;
}
根據section返回對應的cell之間的列最小間距:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 10.0;
}
根據section返回對應的Header大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeMake(collectionView.frame.size.width, 40.0);
}
根據section返回對應的Footer大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return CGSizeMake(collectionView.frame.size.width, 40.0);
}
cell和supplementary view的重用
視圖的重用避免了不斷生成和銷毀對象的操作,提高了程序運行的效率。要想重用cell和supplementary view,首先需要注冊cell和supplementary view,有種三種注冊方式:
- 使用storyboard布局時,直接拖拽cell或者supplementary view到storyboard中,設置好重用標識即可。
- 使用xib布局時,設置重用標識后,使用
registerNib:forCellWithReuseIdentifier:
方法來注冊cell,使用registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
方法來注冊supplementary view。 - 使用代碼布局時,使用
registerClass:forCellWithReuseIdentifier:
方法來注冊cell,使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
方法來注冊supplementary view。
注意:使用純代碼自定義cell和supplementary view時,需要重寫
initWithFrame:
方法,init
方法不會被調用。
dataSource
對象為集合視圖配置cell和supplementary view時,使用dequeueReusableCellWithReuseIdentifier:forIndexPath:
方法直接從重用隊列中取cell,使用dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
方法直接從重用隊列中取supplementary view。當重用隊列中沒有可復用的視圖時,會自動幫我們新創建一個可用的視圖。
cell的插入,刪除和移動
插入,刪除,移動單個cell或者某個section的所有cell時,遵循下面兩個步驟:
- 更新數據源對象中的數據內容。
- 調用對應的插入,刪除或者移動方法。
集合視圖插入,刪除和移動cell之前,必須先對應更新數據源。如果數據源沒有更新,程序運行就會崩潰。當插入,刪除或者移動cell時,會自動添加動畫效果來反映集合視圖的更改。在執行動畫時,如果還需要同步
執行其他操作,可以使用performBatchUpdates:completion:
方法,在updates block
內執行所有插入,刪除或移動調用,動畫執行完畢后會調用completion block
。
[self.collectionView performBatchUpdates:^{
// 執行更改操作
} completion:^(BOOL finished){
if (finished)
{
// 執行其他操作
}
}];
長按cell彈出編輯菜單
長按某個cell時,可以彈出一個編輯菜單,能夠用于剪切,粘貼,復制這個cell。長按彈出編輯菜單,delegate
對象必須實現下面3個委托方法:
是否顯示編輯菜單:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
可以執行哪些操作:
- (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;
}
點擊菜單中選項后回調:
- (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:"])
{
// 復制操作
}else if ([NSStringFromSelector(action) isEqualToString:@"paste:"])
{
// 粘貼操作
}
}
集合視圖只支持cut:
,copy:
,paste:
三種編輯操作。想要了解如何配合剪貼板使用這些操作,可以參看Text Programming Guide for iOS。
切換布局時的轉場動畫
切換布局最簡單的方式是使用setCollectionViewLayout:animated:
方法。在UICollectionViewController
之間跳轉時,如果需要交互式轉場切換布局或者控制切換過程,可以使用UICollectionViewTransitionLayout
對象。
UICollectionViewTransitionLayout
類是一種特殊的布局類,它繼承自UICollectionViewLayout
類,在切換到新布局的過程中,它將作為集合視圖的臨時布局。使用UICollectionViewTransitionLayout
布局對象時,可以使用不同的計時算法讓動畫遵循非線性路徑,或者根據傳入的觸摸事件進行移動。官方提供的UICollectionViewTransitionLayout
類支持對新布局的線性轉換,但我們可以對其進行子類化來實現任何所需的效果。
UICollectionViewLayout
提供了幾種跟蹤布局之間轉換進度的方法,UICollectionViewTransitionLayout
類通過transitionProgress
屬性來跟蹤轉場切換的進度,當轉場切換開始后,需要定期更新此屬性值來指示完成的百分比。使用自定義UICollectionViewTransitionLayout
對象時,UICollectionViewTransitionLayout
類提供來2種跟蹤與布局相關的值的方法:updateValue:forAnimatedKey:
和valueForAnimatedKey:
。
轉場切換布局時,使用UICollectionViewTransitionLayout
對象的步驟如下:
- 使用
initWithCurrentLayout:nextLayout:
方法創建一個UICollectionViewTransitionLayout
實例對象。 - 定期修改
transitionProgress
屬性值來指示轉場切換的進度。在修改轉場進度后,一定要調用invalidateLayout
方法來廢棄當前布局并更新布局。 - 集合視圖的
delegate
對象實現委托方法collectionView:transitionLayoutForOldLayout:newLayout:
返回創建的UICollectionViewTransitionLayout
實例對象。 - 可以使用
updateValue:forAnimatedKey:
方法來修改與布局相關的值。
進階
流水布局
官方提供的UICollectionViewFlowLayout
流水布局對象實現了基于行的斷開布局,單元格被放置在線性路徑上,并沿著該行放置盡可能多的單元格,當前行上的空間在使用最小間距也不足以放置下一個單元格時,會重新計算出合適的當前行上擺放的單元格之間的間距,如果該行上只有一個單元格,那么它會被置中,然后會創建新的一行并在該行重復之前的布局過程。
使用時,通過固定單元格的大小和單元格之間的最小間距來實現網格狀視圖,同時也可以任意設置單元格的大小和單元格之間的間距來實現不規則排列的視圖。當單元格的大小,單元格之間的最小間距,單元格到所在分區四周的邊距以及Header和Footer的大小固定時,可以直接設置itemSize
,minimumLineSpacing
,minimumInteritemSpacing
,sectionInset
,headerReferenceSize
,footerReferenceSize
屬性值。如果想要動態設置它們,需要集合視圖的delegate
對象實現UICollectionViewDelegateFlowLayout
協議的委托方法。
自定義布局
理解布局過程
子類化UICollectionViewLayout
實現自定義布局有兩個關鍵任務需要完成:
- 指定可滾動內容區域的大小。
- 為每個單元格和補充視圖提供布局屬性對象以便集合視圖定位。
集合視圖和自定義布局對象一起工作來管理整體布局過程,當集合視圖需要用到布局信息時,它會請求布局對象提供這些布局信息。調用布局對象的invalidateLayout
方法會告知集合視圖顯式更新其布局,此方法會廢棄現有的布局屬性,并強制布局對象生成新的布局屬性。
不要將布局對象的invalidateLayout
方法與集合視圖的reloadData
方法混淆,調用invalidateLayout
方法不一定會移除當前現有的單元格和子視圖,它只會強制布局對象重新計算移動、添加或刪除單元格時所需的所有布局信息。如果數據源對象提供的數據發生了更改,則應該調用reloadData
方法。使用這兩種方法來更新布局時,實際的布局過程都是一樣的。
在布局過程中,集合視圖會始終按順序來調用布局對象的以下三種方法:
- (void)prepareLayout
- (CGSize)collectionViewContentSize
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
集合視圖調用布局對象的prepareLayout
方法,提供機會讓我們提前計算確定布局屬性信息時所需的數據,從計算出來的數據中要能夠得知集合視圖整個內容區域的大小。
集合視圖調用布局對象的collectionViewContentSize
方法獲得內容大小來適當的配置其滾動視圖,在這里根據提前計算的數據返回整個內容區域的大小。如果內容大小在垂直和水平方向上都超出當前設備屏幕的邊界,則會允許滾動視圖同時在這兩個方向上滾動,而UICollectionViewFlowLayout
只能在一個方向上滾動。
集合視圖會基于當前的滾動位置調用layoutAttributesForElementsInRect:
方法來查找在特定區域中的單元格和視圖的布局屬性,此區域和可視區域可能相同也可能不同,在這里遍歷提前生成的所有的布局屬性信息,檢查每個布局信息的frame,返回所有frame和給定rect相交的布局屬性,這樣核心布局過程就完成了。
可以在prepareLayout
方法中生成布局屬性對象后緩存起來,也可以在layoutAttributesForElementsInRect:
方法中生成布局屬性對象,但是集合視圖在滾動過程中會多次調用layoutAttributesForElementsInRect:
方法,這樣就會為視圖重復計算布局屬性,會有性能損耗。
布局完成后,單元格和視圖的布局屬性會保持不變。調用布局對象的invalidateLayout
會廢棄當前所有布局信息,然后再次從調用prepareLayout
方法開始,重復布局過程生成新的布局信息。集合視圖在滾動過程中,會不斷調用布局對象的shouldInvalidateLayoutForBoundsChange:
方法來判斷是否需要廢棄當前布局并重新生成布局。當集合視圖的bounds
屬性發生變化時,也會調用shouldInvalidateLayoutForBoundsChange:
方法。
調用invalidateLayout
方法后不會立即開始布局更新過程,該方法僅將布局標記為與數據不一致并需要更新。在下一個視圖更新周期中,集合視圖會檢查其布局是否為臟,如果是,則更新布局。也就是說,當我們快速連續地調用invalidateLayout
方法多次后,不會每次調用都立即更新布局。
創建布局信息對象
官方提供了三種方法來創建UICollectionViewLayoutAttributes
布局信息對象:
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath
要根據視圖的類型調用對應的方法來生成布局屬性對象,因為集合視圖會根據布局信息對象的representedElementCategory
屬性從數據源對象中獲取對應類型的視圖,使用錯誤的方法生成布局信息對象會導致集合視圖在錯誤的位置創建錯誤的視圖。
生成布局屬性對象后,一定要根據前面提前計算的數據設置好frame
或者center
和size
屬性,使集合視圖能夠確定對應的視圖的位置和大小。同時,還可以設置transform
,alpha
,hidden
等屬性來控制對應視圖的視覺效果。如果視圖的布局是重疊的,則可以設置zIndex
屬性值來確保視圖的順序一致。如果官方提供UICollectionViewLayoutAttributes
標準類無法滿足需求,可以對其子類化并擴展,以存儲和視圖外觀有關的信息。當對布局屬性進行子類化時,需要實現用于比較自定義屬性的isEqual:
方法,因為集合視圖對其某些操作使用此方法。
根據需要為單個視圖提供布局屬性
布局對象還需要能夠根據需要為單個視圖提供布局屬性,因為集合視圖會在執行Cell的插入,刪除,移動和刷新動畫時請求該布局信息。需要覆寫下面三種方法:
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath
在三種方法中,需要返回已計算好的對應視圖的布局屬性信息,返回屬性時,不應更改布局屬性。如果布局中不包含任何補充視圖和裝飾視圖,則不需要覆寫后兩種方法。
自定義cell的插入、刪除、移動和刷新動畫
集合視圖調用對應的方法插入、刪除、刷新、移動cell時,布局對象會調用invalidateLayout
方法廢棄現有的布局信息,重新執行前面提到的布局過程生成新的布局屬性。在集合視圖更新前調用prepareForCollectionViewUpdates:
方法告知要更新的cell在更新前的indexPath
和更新完成后的indexPath
,以及其要執行的更新方式,需要重寫此方法記錄這些indexPath
。
之后,集合視圖會執行兩個動畫:更新布局前每個cell被移除的動畫和更新布局后每個cell顯示的動畫,我們看到的動畫效果是由這兩個動畫組合而成的。在執行動畫過程中,布局對象會調用finalLayoutAttributesForDisappearingItemAtIndexPath:
方法獲取對應indexPath
的cell被移除時的最終布局屬性來執行動畫:更新布局前的布局屬性值-->cell被移除時的最終布局屬性值,調用initialLayoutAttributesForAppearingItemAtIndexPath:
方法獲取對應indexPath
的cell顯示時的起始布局屬性來執行動畫:cell顯示時的起始布局屬性值-->更新布局后的cell布局屬性值。
插入、刪除、移動cell時,會導致其周圍cell的布局屬性發生變化,這些cell會強制執行這個動畫:cell更新布局前的frame-->cell更新布局后的frame,這是官方在內部實現的。在重寫finalLayoutAttributesForDisappearingItemAtIndexPath:
和initialLayoutAttributesForAppearingItemAtIndexPath:
方法設置執行動畫用到的布局屬性時,最好檢查一下傳入的indexPath
與調用prepareForCollectionViewUpdates:
方法時記錄的indexPath
是否一致。