前言:
實現垂直方向的單列表來說,使用UITableView足以;若是需要構建橫向滑動列表、gridView等直線型布局,則使用UICollectionView+UICollectionViewFlowLayout搭建最合適;更復雜的布局,則可以使用UICollectionView+自定義Layout來實現。
譯文:UICollectionViewLayout是一個應該子類化的抽象基類,用來生成collection view的布局信息。 布局對象決定cell, 追加視圖(supplementary views), 和 裝飾視圖(decoration views) 在collection view內部的位置,並在 collection view 獲取的時候提供這些布局信息。collection view將把這些布局信息應用到相應的視圖上以便在屏幕上進行展示。
如果想使用UICollectionViewLayout,你必須把它子類化。在子類化UICollectionViewLayout之前,應該先了解UICollectionViewFlowLayout,看看它是否已經能夠滿足你的布局需求。(注:UICollectionViewFlowLayout是官方提供的子類化的UICollectionViewLayout,能滿足絕大部分需求)
子類化注意事項:
布局對象(layout object)主要用來為collectionview的元素提供位置信息和狀態信息(比如:選中狀態,未選中狀態等)。布局對象並不負責創建視圖,這些視圖是collectionview的數據源(data source)創建的。布局對象只不過定義了這些視圖的位置和大小。
Collection view有三種需要布局的視圖元素。
Cells:
cell是布局中的主要元素,在collection中每一個cell代表了一個單獨的數據項。一個collection view 既可能只有唯一的一組cell,也可能把這一組cell分到多個 段(section)。布局對象的主要功能就是在 collection view的內容區對這些cell進行布局
supplementary views(追加視圖)
追加視圖代表和cell完全不同的數據。補充視圖不支持選中非選中狀態,可以通過追加視圖添加某個段的段頭和段尾,或者整個collection view的段頭和段尾。追加視圖不是必選項,即實不實現都可以。追加視圖的使用和布局也是由布局對象決定的。
Decoration views(裝飾視圖)
裝飾視圖是用來裝飾的視圖,不支持選中非選中狀態,和數據也沒有內在的關聯(不像cell和section需要返回個數)。裝飾視圖可以看作另一種類型的追加視圖,和追加視圖一樣,它們的使用和布局也是由布局對象決定的。
Collectionview在多個不同的時刻都會從它的布局對象獲取這些元素的布局信。每一個呈現在屏幕上的cell和view,布局信息都來自布局對象。同樣,在collection view插入或者刪除item的時候,就會有相應的布局對象被加入或者刪除。需要注意的是,collection view進行的布局限制在屏幕可見的范圍之內。(注:即,即便創建了所有元素的布局對象,但是真正的布局只是在可見的范圍內,超出屏幕的部分沒有布局)
需要重寫的方法 :
每一個布局對象需要實現接下來的幾個方法
[Objective-C]查看源文件復制代碼
(CGSize)collectionViewContentSize;
返回collectionView內容區的寬度和高度,子類必須重載該方法,返回值代表了所有內容的寬度和高度,而不僅僅是可見范圍的,collectionView通過該信息配置它的滾動范圍,默認返回 CGSizeZero。
[Objective-C]查看源文件復制代碼
- (NSArray<__kindofUICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
返回UICollectionViewLayoutAttributes 類型的數組,UICollectionViewLayoutAttributes 對象包含cell或view的布局信息。子類必須重載該方法,並返回該區域內所有元素的布局信息,包括cell,追加視圖和裝飾視圖。
在創建 layout attributes的時候,創建的是相應元素類型(cell, supplementary,decoration)的 attributes對象,比如:
[Objective-C]查看源文件復制代碼
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath; + (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath; + (instancetype)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;
collection view 根據不同的類型區分屬性,並根據這些信息決定創建怎樣的視圖及如何進行管理。
[Objective-C]查看源文件復制代碼
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
返回指定indexPath的item的布局信息。子類必須重載該方法,該方法只能為cell提供布局信息,不能為補充視圖和裝飾視圖提供。
[Objective-C]查看源文件復制代碼
?
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
如果你的布局支持追加視圖的話,必須重載該方法,該方法返回的是追加視圖的布局信息,kind這個參數區分段頭還是段尾的,在collectionview注冊的時候回用到該參數。
[Objective-C]查看源文件復制代碼
?
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath *)indexPath;
如果你的布局支持裝飾視圖的話,必須重載該方法,該方法返回的是裝飾視圖的布局信息,ecorationViewKind這個參數在collectionview注冊的時候回用到
[Objective-C]查看源文件復制代碼
?
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds [font="]
該方法用來決定是否需要更新布局。如果collection view需要重新布局返回YES,否則返回NO,默認返回值為NO。子類重載該方法的時候,基於是否collection view的bounds的改變會引發cell和view布局的改變,給出正確的返回值。如果collection view的bounds改變,該方法返回YES,collection view通過調用
invalidateLayoutWithContext方法使原來的layout失效
這些方法為collection view 在屏幕上布局提供了最基礎的布局信息,如果你不想為追加視圖和裝飾視圖布局,可以不去重載相應的方法。
當collection view的數據發生改變的時候,比如插入或者刪除 item的時候,collection view將會要求布局對象更新相應的布局信息。移動、添加、刪除 items時都必須更新相應的布局信息以便反映元素最新的位置。對於移動的元素, collection view提供了標准的方法獲取更新後的布局信息。而collection view刪除或者添加元素的時候,將會調用一些不同的方法,你應該重載以便提供正確的布局信息:
[Objective-C]查看源文件復制代碼
?
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath*)itemIndexPath
在一個 item被插入到collection view 的時候,返回開始的布局信息。這個方法在 prepareForCollectionViewUpdates:之後和finalizeCollectionViewUpdates 之前調用。collection view將會使用該布局信息作為動畫的起點(結束點是該item在collection view 的最新的位置)。如果返回為nil,布局對象將用item的最終的attributes 作為動畫的起點和終點。
/
[Objective-C]查看源文件復制代碼
?
/返回值是追加視圖插入collection view時的布局信息。該方法使用同initialLayoutAttributesForAppearingItemAtIndexPath:[/font]- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath //返回值是裝飾視圖插入collection view時的布局信息。該方法使用同initialLayoutAttributesForAppearingItemAtIndexPath: - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
返回值是item即將從collection view移除時候的布局信息,對即將刪除的item來講,該方法在 prepareForCollectionViewUpdates: 之後和finalizeCollectionViewUpdates 之前調用。在該方法中返回的布局信息描包含 item的狀態信息和位置信息。 collection view將會把該信息作為動畫的終點(起點是item當前的位置)。如果返回為nil的話,布局對象將會把當前的attribute,作為動畫的起點和終點。
[Objective-C]查看源文件復制代碼
?
//....不用解釋了吧 ..... - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath //....不用解釋了吧 ..... - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath
除了這些方法之外,你也可以重載- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems;做一些和布局相關的准備工作。也可以重載- (void)finalizeCollectionViewUpdates;通過該方法添加一些動畫到block,或者做一些和最終布局相關的工作。
通過使上下文無效( Invalidation Contexts )來優化布局:
在自定義布局的時候,應該通過使那些布局真正發生改變的部分無效來提升布局性能。在你改變item的時候,通過調用invalidateLayout方法強制collection view重新計算全部的布局信息,並應用該布局信息。一個更好的方式是只重新計算發生改變的布局信息,這恰恰是Invalidation Contexts允許你去做的。你可以通過Invalidation Context指明布局發生改變的部分,布局對象就可以根據該信息 減小重新計算的數據量。
通過子類化 UICollectionViewLayoutInvalidationContext,為你的布局定義一個 自定義的 invalidation context。在子類中定義一些屬性,這些屬性代表布局中可以單獨重新計算的數據。當你需要 invalidate 你的布局時,創建一個 invalidation context 子類的實例,配置自定義的屬性,並把該實例傳給invalidateLayoutWithContext: 方法。你自定義的方法可以根據invalidation context 中的信息重新計算布局改變的部分。
如果你定義了一個自定義的 invalidation context 類,你也應該重載invalidationContextClass方法,返回自定義的類。 collection view 在需要invalidation context時,總是會創建一個指明的類實例。返回你自定義的子類,確保了自定義的對象擁有正確的 invalidation context。
獲取 Collection View 的信息:
[Objective-C]查看源文件復制代碼
?
//當一個新的布局對象分配給collection view時,collection view將設置該屬性的值 @property(nonatomic, readonly) UICollectionView *collectionView //上面已經介紹過,不再介紹 - collectionViewContentSize;
+ (Class)layoutAttributesClass
返回創建布局信息時用到的類,如果你創建了繼承自 UICollectionViewLayoutAttributes的子類,你也應該重載該方法,返回該子類。該方法主要是為了子類化,無需在代碼中調用。
- (void)prepareLayout
該方法將通知布局對象更新當前的布局。布局更新發生在 collection view 第一次展示它的內容的時候,以及由於view的改變導致布局 invalidated 的時候。在布局更新期間, collection view都會首先調用該方法,允許布局對象對此次的更新做一些准備操作。
默認情況下,該方法不會做任何操作。子類可以重載該方法,在方法內部做一些和布局相關的數據創建或計算操作。
[Objective-C]查看源文件復制代碼
?
- (UICollectionViewLayoutAttributes *)layoutAttributesForInteractivelyMovingItemAtIndexPath:(NSIndexPath *)indexPath withTargetPosition:(CGPoint)position [font="]
當item在手勢交互下移動時,通過該方法返回這個item布局的attributes 。默認實現是,復制已存在的attributes,改變attributes兩個值,一個是中心點center;另一個是z軸的坐標值,設置成最大值。所以該item在collection view的最上層。子類重載該方法,可以按照自己的需求更改attributes,首先需要調用super類獲取attributes,然後自定義返回的數據結構。
[Objective-C]查看源文件復制代碼
?
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset [font="]
在進行動畫式布局的時候,該方法返回內容區的偏移量。在布局更新或者布局轉場的時候,collection view 調用該方法改變內容區的偏移量,該偏移量作為動畫的結束點。如果動畫或者轉場造成item位置的改變並不是以最優的方式進行,可以重載該方法進行優化。 collection view在調用prepareLayout 和 collectionViewContentSize 之後調用該方法
[Objective-C]查看源文件復制代碼
?
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity [font="]
該方法返回值為滑動停止的點。如果你希望內容區快速滑動到指定的區域,可以重載該方法。比如,你可以通過該方法讓滑動停止在兩個item中間的區域,而不是某個item的中間。和collection view更新相關的方法:
[Objective-C]查看源文件復制代碼
?
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems [font="]
當插入或者刪除 item的時候,collection view將會通知布局對象它將調整布局,第一步就是調用該方法告知布局對象發生了什麼改變。除此之外,該方法也可以用來獲取插入、刪除、移動item的布局信息。
[Objective-C]查看源文件復制代碼
?
//該方法上文已介紹 - (void)finalizeCollectionViewUpdates - (NSArray
返回一個NSIndexPath 類型的數組,該數組存放的是將要添加到collection view中的追加視圖的 NSIndexPath 。往collection view添加cell或者section的時候,就會調用該方法。collection view將會在prepareForCollectionViewUpdates: 和finalizeCollectionViewUpdates之間調用該方法。
[Objective-C]查看源文件復制代碼
?
- (NSArray
返回一個NSIndexPath 類型的數組,該數組存放的是將要從collection view中刪除的追加視圖的 NSIndexPath 。從collection view刪除cell或者section的時候,就會調用該方法。collection view將會在prepareForCollectionViewUpdates: 和finalizeCollectionViewUpdates之間調用該方法。
類比)indexPathsToDeleteForDecorationViewOfKind:
[Objective-C]查看源文件復制代碼
?
- (NSIndexPath *)targetIndexPathForInteractivelyMovingItem:(NSIndexPath *)previousIndexPath withPosition:(CGPoint)position
根據item在collection view中的位置獲取該item的index path。第一個參數該item原來的index path,第二個參數是item在collection view中的位置。在item移動的過程中,該方法將collection view中的location映射成相應 index paths。該方法的默認是現實,查找指定位置的已經存在的cell,返回該cell的 index path 。如果在相同的位置有多個cell,該方法默認返回最上層的cell。
你可以通過重載該方法來改變 index path的決定方式。比如,你可以返回z坐標軸最底層cell的index path.當你重載該方法的時候,沒有必要去調用super類該方法。
使布局失效:
- (void)invalidateLayout使當前的布局失效,同時觸發布局更新,可以在任何時間調用該方法更新布局信息。該方法使collection view的布局立即失效,你可以在不觸發多次更新的情況下,在同一個block中多次調用該方法。重載該方法的時候,必須調用super類的該方法。
[Objective-C]查看源文件復制代碼
?
- (void)invalidateLayoutWithContext:(UICollectionViewLayoutInvalidationContext *)context [font="]
該方法使布局的部分區域失效,而不是全局失效。是對invalidateLayout的一種優化,這種優化基於UICollectionViewLayoutInvalidationContext 類型的屬性。如果你自定義了UICollectionViewLayoutInvalidationContext ,就應該重載該方法,並使用自定義的類型。重載該方法的時候,必須調用super類。+ (Class)invalidationContextClass;當你子類化UICollectionViewLayout,並且使用自定義的 invalidation context對象優化布局更新,重載該方法,返回UICollectionViewLayoutInvalidationContext的子類。當 collection view 需要invalidate你的布局的時候,將會用該方法返回值創建正確的invalidation context 對象。
[Objective-C]查看源文件復制代碼
?
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds; [font="]
該方法決定了collection view是否能夠進行布局更新,默認為NO。子類在重載該方法的時候,根據cell或者追加視圖是否發生布局改變,返回正確的值。
[Objective-C]查看源文件復制代碼
?
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newbounds; [font="]
collection view 的bounds發生改變的時候返回的無效上下文,該無效上下文描述了bounds變化後需要做出改變的部分。該方法默認實現是,通過invalidationContextClass方法返回的類創建一個實例,並作為返回值。如果你想獲得一個自定義的無效上下文對象,就要重載invalidationContextClass方法。
你可以通過重載該方法去創建和配置自定義的無效上下文。如果你重載該方法,第一步應該調用super類獲取無效上下文,在獲得該無效上下文後,為它設置自定義的屬性,並返回。
[Objective-C]查看源文件復制代碼
?
- (BOOL)shouldInvalidateLayoutForPreferredLayoutAttributes:(UICollectionViewLayoutAttributes *)preferredAttributes withOriginalAttributes:(UICollectionViewLayoutAttributes *)originalAttributes [font="]
當collection view包含self-sizing(自排列)的cell時,這些cell可以在布局attributes 應用到它之前更改這些attributes。一個自排列的cell指明一個不同於布局對象給出的size的時候,就會這麼做。當cell設置一系列不同的attributes時,collection view將會調用該方法判斷是否需要更新布局,默認返回為NO。
[Objective-C]查看源文件復制代碼
?
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForPreferredLayoutAttributes:(UICollectionViewLayoutAttributes *)preferredAttributes withOriginalAttributes:(UICollectionViewLayoutAttributes *)originalAttributes [font="]
該方法返回值是一個上下文,上下文包含布局中需要改變的信息。默認的實現是,使用invalidationContextClass 方法返回的類創建一個實例,並返回。你可以通過重載該方法去創建和配置自定義的無效上下文。如果你重載該方法,第一步應該調用super類獲取無效上下文,在獲得該無效上下文後,為它設置自定義的屬性,並返回。
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray *)previousIndexPaths previousPosition:(CGPoint)previousPosition;- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray *)indexPaths previousIndexPaths:(NSArray *)previousIndexPaths movementCancelled:(BOOL)movementCancelled;
調整動態的變化:
- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds;
collection view在bounds動態改變或者插入、刪除items之前,調用該方法。可以在該方法進行一些相關的計算,比如可以在該方法內部計算插入和刪除的item初始和最終的位置。你也可用通過該方法添加動畫,這些動畫被用於處理item插入、刪除和bounds改變。
- (void)finalizeAnimatedBoundsChange;
該方法在item插入、刪除和bounds改變動畫完成之後,清空相關的操作。
在布局之間轉換:
- (void)prepareForTransitionFromLayout:(UICollectionViewLayout *)oldLayout;
告知布局對象將會作為新的布局被導入到 collection view,該方法先於轉場之前執行,可以在該方法做一些初始化的操作,生成布局attributes;
- (void)prepareForTransitionToLayout:(UICollectionViewLayout *)newLayout;
告知布局對象作為布局即將從collection view移除,該方法先於轉場之前執行,可以在該方法做一些初始化的操作,生成布局attributes;
- (void)finalizeLayoutTransition;
collection view在獲取從一個布局向另一個布局轉場的時候所有的布局attributes 後,調用該方法。你可以用該方法清空prepareForTransitionFromLayout: 和prepareForTransitionToLayout:生成的數據和緩存。
注冊裝飾視圖:
- (void)registerClass:(Class)viewClass forDecorationViewOfKind:(NSString *)decorationViewKind;- (void)registerNib:(UINib *)nib forDecorationViewOfKind:(NSString *)decorationViewKind;