創(chuàng)建自定義UICollectionView layout

在創(chuàng)建自定義的layout之前,你需要知道UICollectionViewFlowLayout提供的很多特性已經(jīng) 經(jīng)過(guò)優(yōu)化以滿(mǎn)足多種常用的layout。除非是如下情況,否則不建議自定義

1 你所想實(shí)現(xiàn)的外觀并不是網(wǎng)格或者 line-based breaking 布局(items排成一行直到行滿(mǎn),再繼續(xù)往下一行上去排,直到所有items都排列完成),或者必須要在多個(gè)方向上都可以滾動(dòng)

2 需要頻繁地改變所有 Cell的位置,以致于創(chuàng)建自定義layout比修改現(xiàn)有flow layout工作量更省

記住:自定義最難的部分是確定布局中各item位置所需要的計(jì)算

繼承UICollectionViewLayout

繼承UICollectionViewLayout之后只需要重載幾個(gè)提供布局核心特性的方法,其他方法只需按情況重載即可,核心特性如下:

1 指定可滾動(dòng)內(nèi)容區(qū)域的size

2 為布局中的每個(gè)Cell及view提供屬性對(duì)象

layout對(duì)象需要用到datasource以創(chuàng)建collection view的layout對(duì)象,其通過(guò)layout自身的collectionView屬性訪問(wèn)此datasource。需要注意的是,知道 layout過(guò)程中哪些信息可以從collection view中訪問(wèn)到,哪些不可以 是非常重要的。因?yàn)閘ayout過(guò)程中,collection view 是無(wú)法獲知各View的布局以及位置的。所以盡量避免通過(guò) collection view獲取除layout之外的信息。

深入理解布局過(guò)程

collection view完全通過(guò)你自定義的layout對(duì)象管理整個(gè)布局過(guò)程,如 collection view 首次布局或者resize的時(shí)候,會(huì)向布局對(duì)象獲取相關(guān)信息。你也可以手動(dòng)調(diào)用invalidateLayout方法以更新布局對(duì)象,此方法會(huì)強(qiáng)制生成新layout。(需要注意invalidateLayout與reloadData的區(qū)別,在移動(dòng),添加或者刪除item的時(shí)候,需要摒棄原有布局,重新生成新的布局,使用invalidateLayout,而如果只是datasource中的數(shù)據(jù)有更新,這時(shí)需要使用reloadData)

layout過(guò)程中,如下方法提供了layout的基本信息,其他方法也會(huì)被調(diào)用,但如下這些方法總是按如下順序調(diào)用的:

1 prepareLayout方法調(diào)用來(lái)為即將進(jìn)行的layout作前期的計(jì)算

2 collectionViewContentSize方法基于初始計(jì)算,返回整體內(nèi)容區(qū)域的size

3 layoutAttributesForElementsInRect:方法返回指定區(qū)域中cells和views的屬性


演示如上3個(gè)方法的調(diào)用過(guò)程

prepareLayout方法是為確定布局中各cell和view位置做計(jì)算,需要在此方法中算出足夠的信息以供后續(xù)方法計(jì)算內(nèi)容區(qū)域的整體size,collection view使用content size 以正確地配置scroll view。比如 content size 長(zhǎng)寬均超過(guò)屏幕的話(huà),水平與豎直方向的滾動(dòng)都會(huì)被enable。基于當(dāng)前滾動(dòng)位置,collection view會(huì)調(diào)用 layoutAttributesForElementsInRect:方法以請(qǐng)求特定rect(有可能是也可能不是可見(jiàn)rect)中cell和view的屬性。到此,core layout process已經(jīng)結(jié)束了。

layout結(jié)束之后,cells和views的屬性在你或者collection view invalidate布局之前都不會(huì)變,collection view可以在滾動(dòng)的過(guò)程中自動(dòng)invalidate 布局:用戶(hù)滾動(dòng)內(nèi)容過(guò)程中,collection view調(diào)用layout的shouldInvalidateLayoutForBoundsChange:方法,如果返回值為YES則invalidate 布局。(但需要知道的是,invalidateLayout并不會(huì)馬上觸發(fā)layout update process,而是在下一個(gè)view更新周期中,collection view發(fā)現(xiàn)layout已經(jīng)dirty才會(huì)去更新)

創(chuàng)建布局屬性

自定義layout需要返回UICollectionViewLayoutAttributes類(lèi)的對(duì)象,這些對(duì)象可以在很多不同方法中創(chuàng)建,但創(chuàng)建時(shí)間可以根據(jù)具體情況具體決定。如果collectionview未有數(shù)千的item,則prepare layout時(shí)創(chuàng)建會(huì)比在用戶(hù)滾動(dòng)過(guò)程中用到時(shí)再計(jì)算更可取,因?yàn)閯?chuàng)建的這些屬性可以緩存起來(lái)。如果計(jì)算所有屬性并緩存起來(lái)所帶來(lái)的性能消耗比請(qǐng)求時(shí)獲取的消耗更大,則可請(qǐng)求時(shí)再創(chuàng)建相關(guān)屬性對(duì)象。

創(chuàng)建UICollectionViewLayoutAttributes類(lèi)對(duì)象新實(shí)例時(shí),可以使用這樣幾個(gè)方法:layoutAttributesForCellWithIndexPath:,layoutAttributesForSupplementaryViewOfKind:withIndexPath:,layoutAttributesForDecorationViewOfKind:withIndexPath:,基于展示的view類(lèi)型的不同,必須使用正確的類(lèi)方法,因?yàn)閏ollection view使用這些信息向datasource對(duì)象請(qǐng)求適當(dāng)類(lèi)型的view。使用錯(cuò)誤的方法會(huì)引起collection view在錯(cuò)誤的地方創(chuàng)建錯(cuò)誤的view,你所希望呈現(xiàn)的layout就不會(huì)出現(xiàn)。

創(chuàng)建每個(gè)屬性對(duì)象之后,將相應(yīng)View的相關(guān)屬性都設(shè)置上。最少要在layout中設(shè)置view的size和position。如果在你的布局中有view重疊了,需要正確配置zIndex屬性以維持重疊views的一致的有序狀態(tài)。其他屬性可以讓你控制cell或者view的可見(jiàn)性或者外觀表現(xiàn)。如果標(biāo)準(zhǔn)屬性類(lèi)無(wú)法滿(mǎn)足你的需要,可以繼承并對(duì)其進(jìn)行擴(kuò)充以存儲(chǔ)每個(gè)View的其他信息。繼承l(wèi)ayout屬性時(shí),需要實(shí)現(xiàn)屬性的isEqual:方法因?yàn)閏ollectionview需要使用這個(gè)方法。

給定矩形中的items的布局屬性

layout processs的最后,collection view會(huì)調(diào)用你的layout對(duì)象的layoutAttributesForElementsInRect:方法。對(duì)一個(gè)大的可滾動(dòng)的內(nèi)容區(qū)域,collectionview可能只會(huì)請(qǐng)求當(dāng)前可見(jiàn)的那部分區(qū)域中的所有items的屬性。當(dāng)然,這個(gè)方法需要支持獲取任意rect中items的信息,因?yàn)橛锌赡茉诓迦爰皠h除時(shí)需要做動(dòng)畫(huà)效果。

可見(jiàn)區(qū)域中的items

layoutAttributesForElementsInRect:方法的實(shí)現(xiàn)需要遵循如下步驟:

1 遍歷prepareLayout方法產(chǎn)生的數(shù)據(jù)以訪問(wèn)緩存的屬性或者創(chuàng)建新的屬性

2 檢查每個(gè)item的frame以確定是否與layoutAttributesForElementsInRect:方法中指定rectangle有重疊部分

3 對(duì)每個(gè)重疊的item,添加一個(gè)對(duì)應(yīng)的UICollectionViewLayoutAttributes對(duì)象到一個(gè)數(shù)組中

4 返回布局屬性的數(shù)組給collection view

不僅要記住緩存layout信息能夠帶來(lái)性能提升,也要記住不斷重復(fù)為cells創(chuàng)建新layout屬性的計(jì)算代價(jià)是十分昂貴的,足以影響到app的性能。當(dāng)collection view管理的items量很大時(shí),采用在請(qǐng)求時(shí)創(chuàng)建layout屬性的方式是十分合理的。

按需提供布局屬性對(duì)象

collection view會(huì)在正常的layout 過(guò)程之外周期性地讓你提供單個(gè)items的layout對(duì)象。比如為某item配置插入和刪除動(dòng)畫(huà)時(shí)。自定義的layout通過(guò)如下方法提供這些信息:

layoutAttributesForItemAtIndexPath:

layoutAttributesForSupplementaryViewOfKind:atIndexPath:

layoutAttributesForDecorationViewOfKind:atIndexPath:

返回屬性時(shí),不能更新這些layout屬性,如果需要改變layout信息,調(diào)用invalidateLayout,在接下來(lái)的layout周期中更新這些信息。上述方法中l(wèi)ayoutAttributesForItemAtIndexPath:是所有自定義 layout都必須重載的方法,如果有supplementary view和decoration view可以分別重載下面兩個(gè)方法。

可以通過(guò)self.collectionView.collectionViewLayout = [[MyCustomLayout alloc] init];方式也可以在storyboard文件中設(shè)置collection view 的class屬性

讓你的layout更優(yōu)異

除了上述這些必須實(shí)現(xiàn)的方法,還有一些特性能夠改善自定義layout的用戶(hù)體驗(yàn),實(shí)現(xiàn)這些屬性是可選但推薦實(shí)現(xiàn)的。

通過(guò) 附加view 提供內(nèi)容品質(zhì)

supplementary views與Cells分離且有自己的layout屬性,由Datasource提供,且其目的是為app主要內(nèi)容增強(qiáng)信息。與cells一樣,supplementary view也會(huì)經(jīng)歷重用的過(guò)程以最小化collection view使用的資源消耗。所以所有 supplementary view都需要繼承UICollectionReusableView。

添加supplementary view到layout中的過(guò)程如下:

1 注冊(cè)supplementary view到layout對(duì)象中,registerClass:forSupplementaryViewOfKind:withReuseIdentifier: or registerNib:forSupplementaryViewOfKind:withReuseIdentifier:

2 在datasource中實(shí)現(xiàn)collectionView:viewForSupplementaryElementOfKind:atIndexPath:,由于這些view是可重用的,調(diào)用dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:來(lái)獲取可用的view

3 但為Cells創(chuàng)建一樣為supplementary Views創(chuàng)建layout 屬性對(duì)象

4 layoutAttributesForElementsInRect:方法中返回的屬性數(shù)組中包含supplementary view的layout屬性對(duì)象

5 實(shí)現(xiàn)layoutAttributesForSupplementaryViewOfKind:atIndexPath:方法為特定supplementary View返回屬性對(duì)象

處理supplementaryview布局屬性的過(guò)程和cell屬性的過(guò)程一樣,但不同的是supplementary view可以有很多種但只能有一種Cell。這是因?yàn)?supplementary view與它們是分離開(kāi)的,是為了烘托主旨,所以每個(gè)supplementary view方法都會(huì)指明其各類(lèi)以方便正確計(jì)算其特有的屬性。

在layout中添加Decoration Views

Decoration Views是layout UI特征的有效點(diǎn)綴,與cell和supplementary view不同的是,它只做外觀呈現(xiàn)用,所以與datasource無(wú)關(guān)。可以用來(lái)提供自定義背影,在Cells縫隙之間填充,甚至可以掩蓋cell,它完全由layout對(duì)象控制。

在layout中添加Decoration view步驟如下:

1 用registerClass:forDecorationViewOfKind: or registerNib:forDecorationViewOfKind: method方法注冊(cè)自定義的decoration view,但記住是在layout對(duì)象中注冊(cè)

2 layout對(duì)象中l(wèi)ayoutAttributesForElementsInRect:方法中為decoration view創(chuàng)建屬性

3 實(shí)現(xiàn)layoutAttributesForDecorationViewOfKind:atIndexPath:方法并在請(qǐng)求時(shí)返回decoration view的布局屬性

4 選擇性地實(shí)現(xiàn)initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath: 和 finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:方法以處理出現(xiàn)和消失的動(dòng)畫(huà),可參考下面的插入和刪除動(dòng)畫(huà)部分

由于decoration view與cell和supplementary view的創(chuàng)建過(guò)程不同,注冊(cè)class或者 nib即可,最多需要調(diào)用 一個(gè)initWithFrame:方法。但任何decoration view也需要是UICollectionReusableView子類(lèi),因?yàn)?也對(duì)其啟用了回收機(jī)制。

插入和刪除動(dòng)畫(huà)

插入及刪除cell時(shí)collection view會(huì)詢(xún)問(wèn)layout對(duì)象提供一組初始化屬性用于動(dòng)畫(huà),同樣,刪除元素時(shí)會(huì)詢(xún)問(wèn)一組終值屬性。

初始化屬性演示

item插入時(shí),layout對(duì)象提供正要被插入的item的初始化layout信息。在此例中,layout先將Cell的初始化位置設(shè)置到Collection view中間,并將其alpha通道設(shè)置為0,動(dòng)畫(huà)期間,此item會(huì)漸現(xiàn)并從中間移動(dòng)到右下角。下面的代碼描述了如何設(shè)置初始化信息及實(shí)現(xiàn)動(dòng)畫(huà):

插入動(dòng)畫(huà)示例

需要注意的是,上述代碼會(huì)使得此item插入的時(shí)候?qū)λ蠧ell都會(huì)添加此插入的動(dòng)畫(huà),若只想對(duì)插入的item做插入動(dòng)畫(huà),可以檢查 index path是否與傳入prepareForCollectionViewUpdates:方法的item的index path匹配,并只在有匹配到的時(shí)候才進(jìn)行動(dòng)畫(huà),否則只返回super initialLayoutAttributesForAppearingItemAtIndexPath:

delete動(dòng)畫(huà)與插入類(lèi)似,提供正確的final 屬性即可

提升layout的滾動(dòng)體驗(yàn)

滾動(dòng)的時(shí)候scrollview會(huì)根據(jù)當(dāng)前的speed和減速狀況決定最終會(huì)停在哪個(gè)偏移,當(dāng)算出這個(gè)停留位置之后,其會(huì)調(diào)用 targetContentOffsetForProposedContentOffset:withScrollingVelocity:方法是否要改變這個(gè)位置,由于其是在滾動(dòng)過(guò)程中調(diào)用此方法,所以自定義layout可以改變滾動(dòng)的儀停留位置。

下圖展示了調(diào)整滾動(dòng)特性的效果

調(diào)整content offset停到合適的位置

假定collection view開(kāi)始于(0,0),且用戶(hù)向左側(cè)滑,collection view計(jì)算出滾動(dòng)原本會(huì)停下的位置,自定義layout可能會(huì)改變這個(gè)值以確保滾動(dòng)停下的時(shí)候,某個(gè)item正好停留在可見(jiàn)區(qū)域正中間。這個(gè)新值會(huì)成為新的目標(biāo)content offset,且會(huì)從targetContentOffsetForProposedContentOffset:withScrollingVelocity:方法返回。

友情提示

1 items數(shù)較小(數(shù)百),或者items layout信息變化較小 時(shí),可以在prepareLayout中創(chuàng)建并緩存layout信息

2 盡量不要繼承UICollectionView

3 不要在layoutAttributesForElementsInRect:方法中調(diào)用uicollectionview的visiblecells方法,因?yàn)槠鋵?shí)這個(gè)調(diào)用是轉(zhuǎn)化成了向layout對(duì)象請(qǐng)求visible cells

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容

  • 父類(lèi):NSObject UICollectionViewLayout是抽象基類(lèi),你可以使用它的子類(lèi)來(lái)生成對(duì)coll...
    Shmily落墨閱讀 1,780評(píng)論 3 3
  • 最近將 UICollectionView 進(jìn)行了一個(gè)全面的學(xué)習(xí)及總結(jié),參考了網(wǎng)上大量的文章,把官方文檔進(jìn)行了大概翻...
    varlarzh閱讀 21,525評(píng)論 3 94
  • UICollectionView 在 iOS6 中第一次被引入,也是 UIKit視圖類(lèi)中的一顆新星。它和 UITa...
    評(píng)評(píng)分分閱讀 1,648評(píng)論 0 10
  • 想研究下collection view自定義布局,所以通讀apple文檔,順手翻譯記下來(lái),供以后翻閱(水平有限,錯(cuò)...
    ParkinWu閱讀 14,642評(píng)論 9 53
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 3,905評(píng)論 1 22