本人菜鳥(niǎo)小白慨蓝,最近研究了下UICollectionView自定義布局實(shí)現(xiàn)瀑布流等布局静暂,主要是應(yīng)對(duì)公司需求摹迷,產(chǎn)品這么設(shè)計(jì)我也很無(wú)奈啊,初次寫(xiě)文章吉执,如有不對(duì)之處,歡迎大家提出,謝謝府阀。
豎向等寬等間隔瀑布流
先上一張效果圖筆者自定義了CandyFlowLayout繼承自UICollectionViewFlowLayout川队,自定義了幾個(gè)屬性煞聪,其實(shí)就是UICollectionViewFlowLayout的屬性,只是重新命名了而已。
@interface CandyFlowLayout : UICollectionViewFlowLayout
/** default 0 */
@property (nonatomic, assign) UIEdgeInsets sectionInsets;
/** default 0 左右*/
@property (nonatomic, assign) CGFloat minItemSpacing;
/** default 0 上下*/
@property (nonatomic, assign) CGFloat minLineSpacing;
@property (nonatomic, assign) CandyFlowLayoutStyle style;
@property (nonatomic, weak) id<CandyFlowLayoutDelegate> delegate;
/** 瀑布流每行item總數(shù)鲸拥,寬度等分 */
@property (nonatomic, assign) NSInteger waterfallRowNumber;
- (instancetype)initSectionInsets:(UIEdgeInsets)sectionInsets minItemSpacing:(CGFloat)minItemSpacing minLineSpacing:(CGFloat)minLineSpacing;
并自定義了初始化方法撞叨。其中CandyFlowLayoutDelegate協(xié)議主要實(shí)現(xiàn)兩個(gè)方法
@protocol CandyFlowLayoutDelegate <NSObject>
@optional
/** 返回item size */
- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
/** 返回item height 瀑布流時(shí)使用 */
- (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath;
@end
.m文件主要實(shí)現(xiàn)幾個(gè)方法就能自定義布局
(void)prepareLayout // 一定要實(shí)現(xiàn)此方法,筆者將布局信息全部在此重寫(xiě)铁材,當(dāng)然也可以寫(xiě)到每個(gè)item的布局方法中,也就是- (UICollectionViewLayoutAttributes)layoutAttributesForItemAtIndexPath:(NSIndexPath)indexPath方法中奕锌,效果等同著觉。
(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect // 返回存放所有item的布局信息數(shù)組
(UICollectionViewLayoutAttributes)layoutAttributesForItemAtIndexPath:(NSIndexPath)indexPath // 返回單個(gè)item的布局信息
(CGSize)collectionViewContentSize // 返回正確的contentSize,這樣就可以在外部得到contentSize惊暴,筆者主要是應(yīng)對(duì)collectionView無(wú)滑動(dòng)效果設(shè)置正確的height=contentsize.height拧篮。此方法可以不用重寫(xiě)羡洁。
接下來(lái)看下豎向等寬等間隔瀑布流布局代碼:
- (void)createWaterfallItemAttributes {
self.contentMaxHeight = 0;
[self.itemHeights removeAllObjects];
for (NSInteger i = 0; i < self.waterfallRowNumber; i ++) {
// 默認(rèn)都是top
[self.itemHeights addObject:@(self.sectionInsets.top)];
}
// 計(jì)算item width
CGFloat width = (ScreenWidth - self.sectionInsets.left - self.sectionInsets.right - (self.waterfallRowNumber - 1) * self.minItemSpacing) / self.waterfallRowNumber * 1.0;
for (NSInteger i = 0; i < self.numberOfSection; i ++) {
NSInteger numberOfItem = [self.collectionView numberOfItemsInSection:i];
for (NSInteger j = 0; j < numberOfItem; j ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//找出每行最短的一列
NSInteger minIndex = 0;
CGFloat minY = [self.itemHeights[0] floatValue];
for (NSInteger n = 1; n < self.waterfallRowNumber; n ++) {
// 依次取出高度
CGFloat itemY = [self.itemHeights[n] floatValue];
if (minY > itemY) {
minY = itemY;
minIndex = n;
}
}
CGFloat xOffset = self.sectionInsets.left + minIndex * (width + self.minItemSpacing);
CGFloat height = 0;
if (self.delegate && [self.delegate respondsToSelector:@selector(heightForItemAtIndexPath:)]) {
height = [self.delegate heightForItemAtIndexPath:indexPath];
}
CGFloat yOffset = minY;
if (yOffset != self.sectionInsets.top) {
// 不是第一行朴则,要加間隔
yOffset += self.minLineSpacing;
}
// 更新高度
self.itemHeights[minIndex] = @(height + yOffset);
// 更新contentSize height
CGFloat maxHeight = [self.itemHeights[minIndex] floatValue];
if (self.contentMaxHeight < maxHeight) {
// 最短的一列 + 高度 > 之前的最高高度
self.contentMaxHeight = maxHeight + self.sectionInsets.bottom;
}
attribute.frame = CGRectMake(xOffset, yOffset, width, height);
[self.itemAttributes addObject:attribute];
}
}
}
主要思路:找出每行最短的一列帆喇,將下一個(gè)item置于此列下方。那怎樣找出最短的一列呢油啤?筆者用數(shù)組itemHeights來(lái)記錄每列的高度典徘。
1.首先設(shè)置初始默認(rèn)值
for (NSInteger i = 0; i < self.waterfallRowNumber; i ++) {
// 默認(rèn)都是top
[self.itemHeights addObject:@(self.sectionInsets.top)];
}
2.兩個(gè)for循環(huán)嵌套即可遍歷每個(gè)item
//找出每行最短的一列
NSInteger minIndex = 0;
CGFloat minY = [self.itemHeights[0] floatValue];
for (NSInteger n = 1; n < self.waterfallRowNumber; n ++) {
// 依次取出高度
CGFloat itemY = [self.itemHeights[n] floatValue];
if (minY > itemY) {
minY = itemY;
minIndex = n;
}
}
找出最短列的方法如上,minIndex即最短列所在的列數(shù)益咬。此時(shí)最難點(diǎn)已經(jīng)解決逮诲,下面就是設(shè)置frame大小即可。注意設(shè)置完每個(gè)item大小幽告,要更新itemHeights數(shù)據(jù)梅鹦。筆者稍后會(huì)上傳完整代碼。
等高等間隔不等寬的排列布局
筆者主要用于類(lèi)型篩選冗锁,每個(gè)文字寬度不等并且換行齐唆,先上一張效果圖:此布局最主要的難點(diǎn)就在于何時(shí)換行,換行之后的y如何設(shè)置冻河,下面貼出代碼:
- (void)createSameHeightItemAttributes {
self.contentMaxHeight = 0;
// 每行實(shí)際的寬度
CGFloat realWidth = ScreenWidth - self.sectionInsets.left - self.sectionInsets.right;
CGFloat xOffset = 0;
CGFloat yOffset = 0;
for (NSInteger i = 0; i < self.numberOfSection; i ++) {
NSInteger numberOfItem = [self.collectionView numberOfItemsInSection:i];
xOffset = self.sectionInsets.left;
yOffset = self.sectionInsets.top + self.contentMaxHeight;
for (NSInteger j = 0; j < numberOfItem; j ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGSize size = CGSizeZero;
if (self.delegate && [self.delegate respondsToSelector:@selector(sizeForItemAtIndexPath:)]) {
size = [self.delegate sizeForItemAtIndexPath:indexPath];
}
CGFloat width = size.width;
CGFloat height = size.height;
if (xOffset + width > realWidth) {
// 換行
xOffset = self.sectionInsets.left;
yOffset = yOffset + self.minLineSpacing + height;
attribute.frame = CGRectMake(xOffset, yOffset, width, height);
xOffset = xOffset + width + self.minItemSpacing;
// 更新contentSize height
self.contentMaxHeight = yOffset + height + self.sectionInsets.bottom;
} else {
attribute.frame = CGRectMake(xOffset, yOffset, width, height);
xOffset = xOffset + width + self.minItemSpacing;
// 更新contentSize height
self.contentMaxHeight = yOffset + height + self.sectionInsets.bottom;
}
[self.itemAttributes addObject:attribute];
}
}
}
注意之處:判斷換行的關(guān)鍵蝶念,實(shí)際寬度 ScreenWidth - self.sectionInsets.left - self.sectionInsets.right,換行之后x芋绸,y的值要設(shè)置正確,其余無(wú)難點(diǎn)担敌。
特殊處理-首行帶有類(lèi)型名稱(chēng)或者全部等
產(chǎn)品大大要這么設(shè)計(jì)摔敛,筆者只能照辦了,先來(lái)張效果圖:其實(shí)也挺常見(jiàn)的全封,類(lèi)型篩選或者展示時(shí)马昙,時(shí)常帶有標(biāo)題或者全部字樣桃犬。只需要簡(jiǎn)單處理下,再換行的時(shí)候空出每個(gè)section第一個(gè)item的寬度距離即可行楞,下面上代碼:
- (void)createSpecialItemAttributes {
self.contentMaxHeight = 0;
CGFloat realWidth = ScreenWidth - self.sectionInsets.left - self.sectionInsets.right;
CGFloat xOffset = 0;
CGFloat yOffset = 0;
for (NSInteger i = 0; i < self.numberOfSection; i ++) {
NSInteger numberOfItem = [self.collectionView numberOfItemsInSection:i];
xOffset = self.sectionInsets.left;
yOffset = self.sectionInsets.top + self.contentMaxHeight;
for (NSInteger j = 0; j < numberOfItem; j ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGSize size = CGSizeZero;
if (self.delegate && [self.delegate respondsToSelector:@selector(sizeForItemAtIndexPath:)]) {
size = [self.delegate sizeForItemAtIndexPath:indexPath];
}
if (xOffset + size.width > realWidth) {
// 換行攒暇,超過(guò)一行
// 取出每個(gè)secction的第一個(gè)
UICollectionViewLayoutAttributes *firstAttribute = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
CGRect frame = firstAttribute.frame;
// x偏移,空出第一個(gè)width
xOffset = CGRectGetMaxX(frame) + self.minItemSpacing;
yOffset = yOffset + size.height + self.minLineSpacing;
attribute.frame = CGRectMake(xOffset, yOffset, size.width, size.height);
xOffset = xOffset + size.width + self.minItemSpacing;
self.contentMaxHeight = CGRectGetMaxY(attribute.frame) + self.sectionInsets.bottom;
} else {
attribute.frame = CGRectMake(xOffset, yOffset, size.width, size.height);
xOffset = xOffset + size.width + self.minItemSpacing;
self.contentMaxHeight = CGRectGetMaxY(attribute.frame) + self.sectionInsets.bottom;
}
[self.itemAttributes addObject:attribute];
}
}
}
換行之處已添加注釋?zhuān)卦O(shè)x子房,y值即可形用,判斷換行條件相同。
以上的方法都包含了雙層for循環(huán)嵌套证杭,如有小伙伴不喜歡太多嵌套田度,將循環(huán)內(nèi)容代碼添加至- (UICollectionViewLayoutAttributes)layoutAttributesForItemAtIndexPath:(NSIndexPath)indexPath方法即可,原理都是一樣的解愤,看喜歡哪種代碼書(shū)寫(xiě)方式镇饺。
筆者也是小白,正好多次用到了UICollectionViewFlowLayout自定義布局送讲,所以就寫(xiě)篇文章記錄一下奸笤,供有需要的小伙伴參考,如有錯(cuò)誤之處哼鬓,希望各位不吝賜教哈监右!