CHTCollectionViewWaterfallLayout分析及仿寫優(yōu)化

瀑布流已經(jīng)是現(xiàn)在很常用的布局樣式,用collectionView就可以很方便的實現(xiàn),現(xiàn)在github上star最多的就是這個CHTCollectionViewWaterfallLayout,也是我項目中經(jīng)常用的。
時間長了還是發(fā)現(xiàn)有滿足不了需求的地方(比如始終缺失的像tableViewHeader一樣的collectionViewHeader和collectionViewFooter),
還發(fā)現(xiàn)了個bug:當(dāng)section個數(shù)大于1但第一個section中item個數(shù)為0時,第二個section中item的位置會出現(xiàn)錯誤(原因是當(dāng)item==0時依然減去了itemSpacing,給他提了issue,但是沒處理)
再加上其Swift版的庫一直沒更新,
所以還是決定自己仿寫一個,學(xué)習(xí)一下大神的思路,順便修復(fù)發(fā)現(xiàn)的bug,加上自己需要的header和footer,整理下代碼結(jié)構(gòu)。
地址在這里:https://github.com/Phelthas/LXMWaterfallLayout
效果如圖:

CHTCollectionViewWaterfallLayout分析

CHTCollectionViewWaterfallLayout的大致思路是:
每個section有幾個column是固定的,然后根據(jù)insect,spacing等屬性,就可以計算出每一列的寬度,然后根據(jù)delegate返回的itemSize,就可以按比例縮放出實際顯示的itemSize。
然后根據(jù)renderDirection屬性,確定每個item的位置。
CHTCollectionViewWaterfallLayout想做一個跟UICollectionViewFlowLayout一樣簡單易用的類,所以整個結(jié)構(gòu)就是仿照UICollectionViewFlowLayout來的,屬性名稱,協(xié)議名稱等也是,所以看起來非常舒服。

首先看協(xié)議


跟UICollectionViewDelegateFlowLayout幾乎一模一樣,不過sizeForItem變成了required,referenceSize變成了height。
referenceSize變成height我覺得很合理,因為實際用過程中也只會用到height,width肯定是collectionView的寬度;
sizeForItem我覺得可以不用必須,如果沒有設(shè)置,那按照計算出來的columnWidth設(shè)置成正方形就行了嘛,就像UICollectionViewFlowLayout一樣也有個初始值。

再看屬性


也跟UICollectionViewDelegateFlowLayout幾乎一模一樣,多了個columnCount,多了個itemRenderDirection,referenceSize變成了height,還多了minimumContentHeight和一個輔助方法。沒啥好分析的。

還有私有屬性


協(xié)議是繼承UICollectionViewDelegate的,所以layout的delegate是取self.collectionView.delegate作為layout的delegate,用起來跟UICollectionViewFlowLayout完全一樣,舒服~
columnHeights是個二維數(shù)組,保存每個section中每個column的高度,注意是具體某一列的累計高度,不是某個cell或者item的高度!!!這個很重要
unionRects是個比較難理解的東西,作者用它來優(yōu)化layoutAttributesForElementsInRect這個方法,我個人感覺意義不大。。?;蛟S是沒領(lǐng)會到作者的深意吧。。。
headerAttribute和footerAttribute作者用了字典而不是二維數(shù)組,key是對應(yīng)的section。
其他屬性也都很好理解,不說了。

然后看實現(xiàn)

最重要的就是這個prepareLayout這個方法。
最開始初始化各個數(shù)組,然后開始遍歷section計算所有的layoutAttributes。
1, 首先是sectionHeader,CHTCollectionViewWaterfallLayout是讓sectionHeader也受sectionInset影響的,所以sectionHeader的起始x是sectionInset.left, 起始y是sectionInset.top;
而UICollectionViewFlowLayout的sectionHeader是不受sectionInset影響的,所以起始x是0,起始y也是0;
兩種方式都有有道理,我更傾向于UICollectionViewFlowLayout的方式,因為section大小給大了,view可以用空白填補,但是要給小了想設(shè)置不用樣式就難了,
所以我仿寫的時候就就用UICollectionViewFlowLayout的方式了。

2, 然后是將columnHeights中對應(yīng)section的數(shù)組的值全部設(shè)置為sectionHeader.fame.maxY。這一步是為了統(tǒng)一下面計算item位置的起始位置。
我覺得,既然是columnHeights,就不要把sectionHeader之類的高度也計算進(jìn)去了,
所以仿寫的時候采用了另外的計算方式:用一個私有變量contentHeight來保存當(dāng)前的Y值,columnHeights只用來保存對應(yīng)的column的高度,計算位置的時候?qū)烧呒悠饋砑纯?/p>

3, 然后是計算每個item的位置
先計算出item應(yīng)該放在那一列,CHTCollectionViewWaterfallLayout用itemRenderDirection定義了三種排列方式:最短優(yōu)先,從左往右,從右往左;
我感覺用處不是很大,既然是瀑布流了,那應(yīng)該不太在意橫著是怎么拍的,所以仿寫的時候就只保留了最短優(yōu)先這一種方式;
而確定哪個最短的方式也很簡單,就是從columnHeights中找出最短的那一列的index;
然后創(chuàng)建attribute,根據(jù)indexPath設(shè)置frame,添加到數(shù)組即可
也就是這里,CHTCollectionViewWaterfallLayout有個bug:
如果某個section的item個數(shù)為0,那就不應(yīng)該計算item的位置,而CHTCollectionViewWaterfallLayout目前的做法是在計算完成之后,判斷columnHeights對應(yīng)section的columnCount是否為0,
為0則再減去itemSpacing,問題是某個section的columnCount跟itemCount其實沒啥關(guān)系,可能我計劃這個section有2列,但是剛好沒有數(shù)據(jù),itemCount為0,這種情況下判斷就出錯了。。。
我仿寫的做法是:把加spacing放在加item高度之前;首先判斷item是否是該列的第一個,是第一個則不用加spacing,否則再加spacing;目前來看是很好的解決了這個問題~~

4, 然后是sectionFooter,方法同sectionHeader。

到這里其實整個布局所需要的屬性就都已經(jīng)計算出來了,下面只需要按需求重載UICollectionViewLayout的函數(shù),返回對應(yīng)數(shù)據(jù)即可。
這里CHTCollectionViewWaterfallLayout用了一個unionRect數(shù)據(jù),貌似是用來優(yōu)化計算:
寫死了unionSize = 20,然后將所有的item分為幾組,每組20個,計算出每組的unionRect,保存在數(shù)組中。

然后是需要重載的函數(shù)
- (CGSize)collectionViewContentSize;
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path;
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
以上這三個 根據(jù)上面的計算返回即可;
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
這個方法一般都是判斷新舊bounds是否一樣,一樣則返回NO,不一樣返回YES。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
是需要注意的,CHTCollectionViewWaterfallLayout也就是在這里用到了那個unionRects數(shù)組,
前后兩個遍歷找出所有與rect想交的rect,再返回需要的item的attributes
這里也是我最不明白的地方,感覺這個方法對效率的提升很有限吶,
反正我仿寫的時候是直接遍歷所有attributes數(shù)組,返回與rect相交的數(shù)組了。
有知道作者那么寫有什么深意的,望不吝賜教~~~

LXMWaterfallLayout 改進(jìn)及優(yōu)化

1,完全是用Swift3.0寫的,語法什么的應(yīng)該都是最新的,沒什么兼容性問題
2,加入了 collectionViewHeaderHeight和collectionViewFooterHeight兩個屬性,
用法同sectionHeader和sectionFooter,需要在collectionView注冊nib或者class,然后在
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
方法中取出來設(shè)置即可
這里因為collectionView的布局完全受layout控制,所以像tableView一樣直接設(shè)置為collectionView的屬性是不行的,肯定會涉及到layout,目前只想到了這種方式,如果誰有什么好的想法,歡迎討論~
3,加入了默認(rèn)的itemSize實現(xiàn)
所有的協(xié)議方法默認(rèn)都可以不實現(xiàn),不實現(xiàn)的時候,就相當(dāng)是一個每個section固定有幾列,且支持collectionViewHeader和collectionViewFooter的layout
4,自以為代碼寫的還算比較規(guī)范,結(jié)構(gòu)還算清晰,看起來比較舒服(?(? ???ω??? ?)?)

最后編輯于
?著作權(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)容