市面上大多數(shù)的APP首頁面都會有功能菜單,并且通常可以滑動分頁
UICollectionView原生Flow Layout的問題
要制作這樣的功能菜單,首選的解決方案自然是UICollectionView。然而,使用UICollectionView原生的Flow Layout了讨,會有兩個問題:
- UICollectionView設(shè)置成水平方向滾動時,菜單項會變成上下排布,即:
[1][3][5] [7]
[2][4][6]
而我們所期望的是(假設(shè)每頁顯示6個功能項):
[1][2][3] [7]
[4][5][6] - 剩余的item如果不滿一頁前计,則不會單獨占用一頁胞谭。比如有7個item,每頁顯示6個男杈,那么水平滾動UICollectionView丈屹,最終邊界只會固定在第7個item,而不是把第7個item當(dāng)作第2頁的第1個item來顯示
可以看下圖中的效果伶棒,原生的Flow Layout如圖上半部分顯示旺垒,而我們期望的是圖示下半部顯示的效果:
常見的解決方法
對此,有些人會尋找第三方控件實現(xiàn)肤无,而有些人會轉(zhuǎn)而用UIScrollView自行實現(xiàn)先蒋。
使用第三方控件需要熟悉其用法,有時靈活性可能不能滿足要求(例如想自定義item外觀)宛渐,且有的實現(xiàn)是基于UIScrollView的鞭达,沒有像UICollectionView那樣有復(fù)用cell,這在效率上來說皇忿,不是很理想(但通常菜單項不會很多,也不會太要緊)坦仍。
用UIScrollView自行實現(xiàn)鳍烁,需自行計算item的排布,會比較煩瑣繁扎,另外和前面提到的一樣幔荒,效率會有影響。
自定義UICollectionView布局
其實我們可以自定義UICollectionView布局來解決這個問題梳玫,自定義布局也是最為簡單高效的方案爹梁。
這里只簡要說一下實現(xiàn)過程及部分示例代碼,不再贅述自定義布局相關(guān)的基礎(chǔ)知識(對這方面不是很清楚的同學(xué)提澎,請自行查閱相關(guān)文章)姚垃,具體的實現(xiàn)代碼也會在最后給出。
在實現(xiàn)之前盼忌,先要確定一下要實現(xiàn)的功能积糯。自定義布局可以做的很靈活,但靈活就意味著考慮的事情越多谦纱,代碼也會越復(fù)雜看成。秉承KISS原則,就只實現(xiàn)足夠使用的功能跨嘉。
我們假定川慌,每個item的大小是固定不變的,這有助于layout計算排列item的位置。
實現(xiàn)
首先梦重,先創(chuàng)建自定義布局類:
@interface KSTCollectionViewPageHorizontalLayout : UICollectionViewLayout
@property (nonatomic, assign) CGFloat lineSpacing;
@property (nonatomic, assign) CGFloat interitemSpacing;
@property (nonatomic, assign) CGSize itemSize;
@end
在prepareLayout
方法中做相應(yīng)的計算:
- (void)prepareLayout {
// 根據(jù)設(shè)定的itemSize和collectionView的frame來確定有多少行兑燥、多少列
// 計算可以放多少列
NSInteger baseColumnCount = self.collectionView.frame.size.width / (self.itemSize.width + self.interitemSpacing);
// 計算可以放多少行
CGFloat contentHeight = self.collectionView.frame.size.height - self.sectionInsetTop;
NSInteger baseLineCount = contentHeight / (self.itemSize.height + self.lineSpacing);
}
確定好行數(shù)、列數(shù)后忍饰,我們就可以在layoutAttributesForItemAtIndexPath
中對item進(jìn)行相應(yīng)的排布:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// 根據(jù)行數(shù)贪嫂、列數(shù),計算每頁各個item的位置
NSInteger pageItemCount = (self.calculatedLineCount * self.calculatedColumnCount);
NSInteger currentPage = indexPath.row / pageItemCount;
NSInteger currentLine = (indexPath.row - currentPage * pageItemCount) / self.calculatedColumnCount;
NSInteger currentColumn = indexPath.row % self.calculatedColumnCount;
layoutAttributes.frame = CGRectMake(currentPage * self.collectionView.frame.size.width + currentColumn * (self.itemSize.width + self.interitemSpacing), self.sectionInsetTop + currentLine * (self.itemSize.height + self.lineSpacing), self.itemSize.width, self.itemSize.height);
return layoutAttributes;
}
最后艾蓝,因為我們希望剩余的item如果不夠一頁力崇,也能單獨占用一頁顯示,就需要重寫collectionViewContentSize
方法返回適當(dāng)?shù)?code>contentSize:
- (CGSize)collectionViewContentSize {
// 計算item所占的頁數(shù)赢织,如7個item亮靴,每頁6個,則應(yīng)為2頁
// 根據(jù)頁數(shù)計算出適當(dāng)?shù)腸ontentSize
NSInteger sectionItemCount = [self.collectionView numberOfItemsInSection:0];
NSInteger pageItemCount = (self.calculatedLineCount * self.calculatedColumnCount);
NSInteger pageCount = (sectionItemCount + pageItemCount - 1) / pageItemCount;
return CGSizeMake(pageCount * self.collectionView.frame.size.width, 0);
}
完成自定義布局后于置,給CollectionView指定布局即可:
KSTCollectionViewPageHorizontalLayout *pageHorizontalLayout = [[KSTCollectionViewPageHorizontalLayout alloc] init];
pageHorizontalLayout.itemSize = CGSizeMake(SCREEN_WIDTH / 5, 83 * (SCREEN_WIDTH / 375.0));
self.menuCollectionView.pagingEnabled = YES;
self.menuCollectionView.collectionViewLayout = pageHorizontalLayout;
細(xì)節(jié)
- 是否需要item間距及每行的間距
- 對于每一行item茧吊,是左對齊進(jìn)行排布,還是分散對齊進(jìn)行排布
- 是否需要像原生Flow Layout的sectionInset功能
- ……
類似這些細(xì)節(jié)問題可以自行考量八毯,畢竟關(guān)注的細(xì)節(jié)越多搓侄,實現(xiàn)起來也就越復(fù)雜,還是建議視項目需要進(jìn)行取舍话速。
自定義布局的優(yōu)點
- 學(xué)習(xí)成本低讶踪,只需要更換UICollectionView的布局即可,不需要像使用第三方控件那樣泊交,去熟悉第三方控件的屬性乳讥、API
- 靈活性好,要顯示什么樣的item廓俭,都可以自定義cell實現(xiàn)
- 效率高云石,最終還是使用UICollectionView,cell有良好的復(fù)用
- 復(fù)用性好研乒,布局可以用于任意的UICollectionView
參考代碼
具體實現(xiàn)代碼請參考:KSTCollectionViewPageHorizontalLayout