CollectionView自定義布局

想研究下collection view自定義布局,所以通讀apple文檔,順手翻譯記下來,供以后翻閱(水平有限,錯誤在所難免,請原諒我蹩腳的英文)

一、創(chuàng)建自定義layout

在你開始創(chuàng)建一個自定義layout的時候,先考慮一下是否真的需要。
UICollectionViewFlowLayout已經(jīng)提供的特性,可以實現(xiàn)很多不同種類的布局。滿足一下條件,可以考慮用自定義layout:

  • 你需要的layout一點也不像一個網(wǎng)格狀的layout,或者line-based breaking layout(就是,當cell鋪滿一行后,接著再下一行鋪,一直到所有cell展示完畢),或者需要多方向滾動的時候
  • 你想要經(jīng)常改變cell的位置,而且修改flow layout比創(chuàng)建一個自定義layout還要麻煩的時候

解釋一下:

  1. 如果你需要的layout跟UICollectionViewFlowLayout樣式差別過大
  2. 多方向滾動
  3. 修改UICollectionViewFlowLayout比創(chuàng)建一個自定義還麻煩

好消息是API很清晰,實現(xiàn)一個自定義layout并不困難,最難的部分是在布局中通過計算確定每個cell的位置,當你搞定這些信息,提供給collection view是很簡單的。

二、繼承UICollectionViewLayout

對于自定義layout, 你需要繼承UICollectionViewLayout,只有一少部分核心方法必須需要你實現(xiàn)的,其他方法按需實現(xiàn),這些核心方法只要來完成這些重要的任務(wù):

  • 指定能滾動的區(qū)域大小
  • 給cell提供屬性對象, 使你的layout能夠正確的擺放cell(也就是給每個cell定位)

你可以只實現(xiàn)這些核心方法,但如果你實現(xiàn)一些可選方法會讓你的layout看起來更加牛逼!

layout對象可以根據(jù)data source提供的信息創(chuàng)建出collection view 的layout。
你的layout通過調(diào)用collectionView 屬性方法跟data source進行通信,這些屬性在所有l(wèi)ayout方法中都是可以訪問的。
在layout過程中,你要明白,你的collection view知道什么,不知道什么,因為collection view不能追蹤布局或者views的位置,甚至,layout對象不會限制你去調(diào)用任何collection view的方法,所以,別指望collection view幫你計算布局。

三、理解布局過程

collection view布局工作都由自定義layout對象進行處理。當collection view需要布局信息的時候,它會向layout對象要求提供這些信息。

舉個例子,collection view首次顯示或者resize的時候,它會向layout要這些信息。

你也可以調(diào)用layout對象的invalidateLayout方法通知collection view更新自己的布局。這個方法把存在的layout信息全部丟棄,然后layout對象會重新生成布局信息。

注意:不要把invalidateLayout方法跟collection view的reloadData方法搞混了。
不恰當?shù)卣{(diào)用invalidateLayout將導致collection view 廢棄掉已經(jīng)存在的布局,和子視圖
當然了,如果刪除、移動或者添加cell,重新計算所有的布局是有必要的。
如果data source中得數(shù)據(jù)改變了,調(diào)用reloadData更好

在整個布局過程中, collection view 調(diào)用layout對象的方法。
你可以在這些方法中計算cell的位置和給collection view 提供一些必要的信息,其他的方法也可能調(diào)用,但是以下幾個方法在整個布局過程中調(diào)用最為頻繁,且調(diào)用順序如下:

  1. 使用prepareLayout方法為布局計算做一些準備工作
  2. 使用 collectionViewContentSize方法返回內(nèi)容區(qū)域的size
  3. 使用layoutAttributesForElementsInRect:方法返回矩形區(qū)域內(nèi)cells或者views的屬性

5-1 說明了你怎樣使用上述方法產(chǎn)生布局信息

5-1

prepareLayout方法里面做布局需要的所有cells和views位置相關(guān)的計算, 最少你也要在這個方法中計算出內(nèi)容區(qū)域的size,以供第二步返回使用。

collection view 使用content size 配置自己scrollview,舉個例子,當你計算的content size超過設(shè)備的屏幕大小,scrollview便能夠同時橫向和縱向移動了。 不像 UICollectionViewFlowLayout, 它不默認的調(diào)節(jié)布局使之只能一個方向滾動。

基于當前的滾動位置,collection view 會調(diào)用layoutAttributesForElementsInRect:方法獲取指定矩形區(qū)域內(nèi)cells和views的屬性,這個指定區(qū)域跟可見區(qū)域大小可能相同,也可能不相同,返回這些信息之后,核心布局過程已經(jīng)完成了。

布局完成之后,你cells和views中的屬性,會被保留,除非你或者collection view主動廢棄了這些布局。
調(diào)用invalidateLayout會導致布局過程重新開始,再次從prepareLayout開始
collection view滾動的時候,也可能會自動廢棄布局,當用戶滾動它的內(nèi)容的時候,collection view會調(diào)用layout 對象的shouldInvalidateLayoutForBoundsChange:方法,如果該方法返回YES,便會廢棄約束。

注意 記住調(diào)用invalidateLayout方法不會立刻開始更新布局很有用。當數(shù)據(jù)和布局不一致的時候,才需要調(diào)用這個方法,在下一個視圖更新循環(huán)中,collection view會檢查是否自己的約束需要更新,如果需要,就更新,事實上,你可以在一個很短的時間內(nèi)多次調(diào)用invalidateLayout方法,但并不會每次都出發(fā)布局更新

四、創(chuàng)建布局屬性

你layout生成的屬性是UICollectionViewLayoutAttributes的實例變量,這些實例變量可以在你app不同的方法里創(chuàng)建。
當你的app不是處理成千上萬條數(shù)據(jù),你完全可以在prepareLayout方法里面創(chuàng)建,因為你的布局會被緩存和引用。
但如果這樣的成本高過所得到的效率的話,那在屬性使用的時候創(chuàng)建也是很容易的。

不管怎樣,當你新創(chuàng)建一個UICollectionViewLayoutAttributes實例的時候,從以下幾個方法中選一個吧:

  • layoutAttributesForCellWithIndexPath:
  • layoutAttributesForSupplementaryViewOfKind:withIndexPath:
  • layoutAttributesForDecorationViewOfKind:withIndexPath:

對于不同的view,你必須使用正確的類方法,因為collection view會使用這些信息取向data source對象請求view的類型,使用不正確的方法將導致collection view創(chuàng)建錯誤的視圖,你想要的布局也不會出現(xiàn)。

創(chuàng)建每個屬性對象之后,設(shè)置相應(yīng)地屬性到對應(yīng)的view上,最少你要設(shè)置view的大小和位置,view之間有重疊的部分,你需要給zIndex賦值,來保證這些view的層級關(guān)系。其他屬性讓你可以控制是否可見或者外觀,是否可以按照要求改變,如果這些標準的屬性類型不滿足你的需求,你可以實現(xiàn)子類,擴展他們?nèi)ゴ鎯ζ渌麑傩?。當你使用了子類屬性對象,你必須實現(xiàn)isEqual:方法,用來比較屬性,因為collection view一些操作用到了這個方法。

五、 準備布局(Preparing the Layout)

在布局開始的時候,layout對象會先調(diào)用prepareLayout方法,這個方法里面你可以計算一會兒你要用到的信息。 prepareLayout方法并不是必須實現(xiàn)的,但是它給你一個機會去做一些必要地初始化計算。
這個方法調(diào)用后,你計算出來的信息必須能夠計算出collection view的content size.

六、提供布局屬性

布局的最后一步,collection view會調(diào)用layoutAttributesForElementsInRect:方法,這個方法的目的就是提供指定區(qū)域內(nèi)cells,supplementary,或者decoration view需要的屬性。
如果是一個很大的滾動區(qū)域,collection view可能只是需要可見區(qū)域的屬性, 在圖 5-2中, 需要就是6-20和第二個headerview的布局屬性, 你必須準備好這些布局屬性。這些屬性可能用來做刪除插入動畫。

5-2

因為這個layoutAttributesForElementsInRect方法在prepareLayout之后調(diào)用,所以你應(yīng)該已經(jīng)有了絕大多數(shù)的信息取創(chuàng)建并返回需要的屬性,實現(xiàn)layoutAttributesForElementsInRect方法需要以下幾步:

  1. 遍歷所有prepareLayout生成的數(shù)據(jù),決定是訪問緩存還是創(chuàng)建一個新的。
  2. 檢查每個item的frame,確保在layoutAttributesForElementsInRect給的矩形區(qū)域內(nèi)(可交叉)
  3. 對于每個符合步驟2條件的item,添加對應(yīng)的UICollectionViewLayoutAttributes對象到一個數(shù)組
  4. 返回數(shù)組給collection view

取決于你怎樣管理你的布局信息,你可能會在prepareLayout方法,或者在layoutAttributesForElementsInRect方法中創(chuàng)建UICollectionViewLayoutAttributes對象。
不管使用哪種方式,謹記?效率,重復計算一個新布局屬性是非常昂貴的操作,這樣對你app的體驗是非常有害的。換個說法,當你collection view item數(shù)量巨大,你應(yīng)該考慮在需要的時候才去創(chuàng)建這些屬性,這是一個很簡單的策略。

注意: layout對象也需要能夠為一些item立刻提供屬性,collection view可能會因為一些特殊原因,包括創(chuàng)建動畫,去要求這些信息

七、立刻提供布局屬性

collection view會定期向你的layout對象要求特殊的屬性,舉個例子,當你配置插入和刪除動畫的時候,collection view會要求這些信息,你的layout對象必須準備好為這些cell,supplementary,decoration提供支持布局屬性,你可以復寫一下方法取做這件事:

  • layoutAttributesForItemAtIndexPath:
  • layoutAttributesForSupplementaryViewOfKind:atIndexPath:
  • layoutAttributesForDecorationViewOfKind:atIndexPath:

有時限這些方法應(yīng)該取回cell或者view的布局屬性,每個自定義布局對象都有必要實現(xiàn)layoutAttributesForItemAtIndexPath:這個方法。如果你的布局不包含任何supplementary views,你不用實現(xiàn)layoutAttributesForSupplementaryViewOfKind:atIndexPath這個方法,同樣地,如果不包含decoration views, 你也不用實現(xiàn)layoutAttributesForDecorationViewOfKind:atIndexPath:這個方法,當返回這些屬性的時候,你不應(yīng)該更新這些屬性,如果你需要更改布局信息,廢棄掉這個layout 對象,讓它重新更新,重新開始一個布局過程。

八、使用你的自定義布局

這里有兩個方法可以是使用你的自定義布局:純代碼,和通過storyboard,collection view會通過一個屬性與你的自定義布局相關(guān)聯(lián)--collectionViewLayout.

self.collectionView.collectionViewLayout = [[MyCustomLayout alloc] init];

九、讓你的自定義布局更好

為每個cell提供布局屬性是必要的,但是你的layout還有其他可以提升用戶體驗的特性,實現(xiàn)這些特性不是必須的,但非常建議。

十、讓插入和刪除動畫更有趣

插入和刪除cells和views是一個非常有趣的問題, 插入一個cell會造成其他cell和view布局的改變。
因為layout對象知道怎樣對已經(jīng)存在的cell和view從當前位置移動到一個新位置做動畫, 但是,它并不知道新cell會被插入的位置,無動畫的插入一個新的cell,collection view為了做這個動畫,會向layout對象要求提供一系列屬性。當一個cell被刪除的時候,過程也相似。

去理解這些初始化屬性怎樣工作,看一個例子是很有幫助的,圖5-3展示了一個只有三個cell的collection view,當一個新的cell被插入的時候,layout對象會提供給collection view這個cell的初始屬性。這樣,layout對象會設(shè)置cell的位置到collection view的中間,并且把alpha值從0設(shè)置為1,在動畫期間,這個新cell會漸漸地出現(xiàn)移動到collection view的中央,最后的位置在右下角。

5-3

5-2展示了相關(guān)代碼


- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {

   UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];

   attributes.alpha = 0.0;

   CGSize size = [self collectionView].frame.size;

   attributes.center = CGPointMake(size.width / 2.0, size.height / 2.0);

   return attributes;

}

注意:當,一個cell插入的時候,5-2 代碼會將所有的cell都做動畫,但第四個之前的cell已經(jīng)展示完畢了,再做動畫也不合適。只為剛插入的cell做動畫,檢查一下這個方法的index path是否跟prepareForCollectionViewUpdates:傳入的index path一致, 如果一致,則做動畫,否則就調(diào)用super的initialLayoutAttributesForAppearingItemAtIndexPath:方法

刪除的處理過程跟插入的完全一致,除了你需要指定最終屬性,而不是初始實行,根據(jù)剛才的例子,如果你使用相同的屬性刪除一個cell,cell會慢慢消失同時移動到collection view的中間,在UICollectionViewLayout中有六個方法可用--兩個分離的方法(初始參數(shù)和最終參數(shù))

十一、提升滾動體驗

你自定義layout對象會影響滾動的效果去創(chuàng)建一個更好地體驗。當滾動相關(guān)的觸摸事件結(jié)束后,scrollview會根據(jù)當前的速度和減速率決定最后靜止的內(nèi)容區(qū)域,當collection view知道了這個位置,它會調(diào)用layout對象的targetContentOffsetForProposedContentOffset:withScrollingVelocity:方法,是否位置需要改變。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容