collectionView瀑布流的設(shè)計與實現(xiàn)

話不多說,先看效果:

collectionView瀑布流.gif

這是我前幾天在項目空檔期季俩,仿的小紅書钮糖,它的主頁就是典型的瀑布流。下面咱們就分析一下:

思路:

  • collectionViewCell的大小是自適應(yīng)的,即高度不固定店归。所以這個cell阎抒,我們需要自定義。我是xib搭的消痛,簡單高效且叁。
  • collectionView中cell的布局是錯位的。所以我們要做的是在視圖加載時秩伞,找出高度短的那一列逞带,在它下面添加其它cell。

難點和關(guān)鍵點就是上面分析的這兩個方面纱新。下面咱們逐一解決:

  1. 首先展氓,這個高度自適應(yīng)的cell該怎么搭建呢?其實so easy怒炸。就是xib带饱,設(shè)置控件約束的問題。那我們該怎么設(shè)置約束呢阅羹?這個cell的高度其實是因內(nèi)部的imageView的高度變化的。所以教寂,我們只講imageView的約束設(shè)置捏鱼。估計,我不說酪耕,大家也知道怎么做导梆。就是,不設(shè)置imageView高度的約束迂烁。然后設(shè)置以下約束:寬度等于cell的寬度看尼,距離cell左側(cè)為0,頂部top為0盟步,距離下方控件一定距離藏斩。這樣就OK了。

  2. 接下來要講的才是重中之重却盘!
    2.1 重寫布局類(MCWaterFlowLayout)狰域,創(chuàng)建一個繼承UICollectionViewFlowLayout的布局類。
    2.2 重寫一些父類方法:
    重寫prepareLayout方法
    作用:在這個方法做一些初始化操作黄橘。
    重寫layoutAttributesForElementsInRect方法
    作用:返回當前屏幕視圖框內(nèi)item的屬性兆览,可以直接返回所有item屬性,指定區(qū)域的cell布局對象.定新的區(qū)域的時候調(diào)用
    重寫collectionViewContentSize方法
    作用:決定collectionView的可滾動范圍

  3. 直接上代碼:
    MCWaterFlowLayout.h

@interface MCWaterFlowLayout : UICollectionViewFlowLayout
@optional
-(NSInteger)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout columnCountForSection:(NSInteger)section;
-(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForHeaderInSection:(NSInteger)section;
-(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForFooterInSection:(NSInteger)section;
@end
@protocol 
@interface MCWaterFlowLayout : UICollectionViewFlowLayout
@property (nonatomic, assign) NSInteger columnCount; // default 2
@property (nonatomic, assign) CGFloat headerHeight; // default 0
@property (nonatomic, assign) CGFloat footerHeight; // default 0
// contentsize的高度
@property (nonatomic, assign) CGFloat contentHeight;
@end

MCWaterFlowLayout.m

@interface MCWaterFlowLayout()
@property (nonatomic, weak) id<MCWaterFlowLayoutDelegate> delegate;
// 存放所有item的attrubutes屬性
@property (nonatomic, strong) NSMutableArray *itemAttributes;
// 存放所有段頭或斷尾的attrubutes屬性
@property (nonatomic, strong) NSMutableArray *supplementaryAttributes;
@end
@implementation MCWaterFlowLayout
-(id)init
{
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {  
        [self setup];
    }
    return self;
}
//設(shè)置默認屬性
-(void)setup
{
    self.minimumInteritemSpacing = 10.0f;
    self.minimumLineSpacing = 10.0f;
    self.sectionInset = UIEdgeInsetsMake(0.0f, 10.0f, 10.0f, 10.0f);
    _columnCount = 2;
    _itemAttributes = [NSMutableArray array];
    _supplementaryAttributes = [NSMutableArray array];
}/**
 *  準備好布局時調(diào)用
 */
-(void)prepareLayout
{
    self.contentHeight = 0;
    [self.itemAttributes removeAllObjects];
    [self.supplementaryAttributes removeAllObjects];
    
    NSInteger numberOfSections = [self.collectionView numberOfSections];
    
    for (NSInteger section = 0; section < numberOfSections; section++) {
        CGFloat minimumInteritemSpacing = [self minimumInteritemSpacingForSection:section];
        CGFloat minimumLineSpacing = [self minimumLineSpacingForSection:section];
        UIEdgeInsets sectionInset = [self sectionInsetForSection:section];
        NSInteger columnCount = [self columnCountForSection:section];
        CGFloat headerHeight = [self headerHeightForSection:section];
        CGFloat footerHeight = [self footerHeightForSection:section];
        
        NSMutableDictionary *supplementary = [[NSMutableDictionary alloc] initWithCapacity:2];
        
        self.contentHeight += sectionInset.top;
        
        //header  設(shè)置段頭視圖的frame
        if (headerHeight > 0) {
            UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
            attributes.frame = CGRectMake(0, self.contentHeight, self.collectionView.frame.size.width, headerHeight);
            
            [self.itemAttributes addObject:attributes];
            [supplementary setObject:attributes forKey:UICollectionElementKindSectionHeader];
            
            self.contentHeight = CGRectGetMaxY(attributes.frame);
        }
        
        //cellitem  設(shè)置cell視圖的frame
        NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
        
        NSMutableArray *columnHeights = [[NSMutableArray alloc] initWithCapacity:columnCount];
        
        for (NSInteger i = 0; i < columnCount; i++) {
            columnHeights[i] = @(self.contentHeight);
        }
        
        for (NSInteger i = 0; i < itemCount; i++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
            
            //返回數(shù)組最小值對應(yīng)的索引值,即找出位置高度最短的一列
            NSInteger columnIndex = [columnHeights indexOfObject:[columnHeights valueForKeyPath:@"@min.self"]];
            
            CGSize size = [self itemSizeForIndexPath:indexPath];
            CGFloat x = sectionInset.left + (size.width + minimumInteritemSpacing) * columnIndex;
            
            UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            if (indexPath.row == 0) {
                attributes.frame = CGRectMake(x, [columnHeights[columnIndex] floatValue], size.width, size.height);
            }
            attributes.frame = CGRectMake(x, [columnHeights[columnIndex] floatValue], size.width, size.height);
            
            [self.itemAttributes addObject:attributes];
            
            columnHeights[columnIndex] = @(CGRectGetMaxY(attributes.frame) + minimumLineSpacing);
        }
        
        self.contentHeight = [[columnHeights valueForKeyPath:@"@max.self"] floatValue];
        
        if (itemCount == 0) {
            self.contentHeight += [UIScreen mainScreen].bounds.size.height;
        }
        
        //footer    設(shè)置段尾視圖的frame
        if (footerHeight > 0) {
            UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
            attributes.frame = CGRectMake(0, self.contentHeight, self.collectionView.frame.size.width, footerHeight);
            
            [self.itemAttributes addObject:attributes];
            [supplementary setObject:attributes forKey:UICollectionElementKindSectionFooter];
            
            self.contentHeight = CGRectGetMaxY(attributes.frame);
        }
        [self.supplementaryAttributes addObject:supplementary];
        self.contentHeight += sectionInset.bottom; 
    }
}
-(CGSize)collectionViewContentSize
{
    CGSize size = CGSizeMake(self.collectionView.frame.size.width, self.contentHeight);
    return size;
}
/**
 *  返回當前屏幕視圖框內(nèi)item的屬性塞关,可以直接返回所有item屬性,指定區(qū)域的cell布局對象.定新的區(qū)域的時候調(diào)用
 */
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    //判斷矩形結(jié)構(gòu)是否交叉抬探,兩個矩形對象是否重疊
    NSArray *array = [self.itemAttributes filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) {
//        NSLog(@"%@*********%@",NSStringFromCGRect(rect),NSStringFromCGRect(evaluatedObject.frame));
        return CGRectIntersectsRect(rect, evaluatedObject.frame);
    }]];
    return array;
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger index = indexPath.item;
    
    for (NSInteger section = 0; section < indexPath.section; section++) {
        index += [self.collectionView numberOfItemsInSection:section];
    }
    
    return self.itemAttributes[index];
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    return self.supplementaryAttributes[indexPath.section][kind];
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    CGRect oldBounds = self.collectionView.bounds;
    
    if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) {
        return YES;
    }
    return NO;
}
#pragma mark -
-(id<MCWaterFlowLayoutDelegate>)delegate
{
    if (_delegate == nil) {
        _delegate =  (id<MCWaterFlowLayoutDelegate>)self.collectionView.delegate;
    }
    return _delegate;
}
-(NSInteger)columnCountForSection:(NSInteger)section
{
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)]) {
        self.columnCount = [self.delegate collectionView:self.collectionView layout:self columnCountForSection:section];
    }
    return self.columnCount;
}
/**
 *  獲取段頭的高度
 */
-(CGFloat)headerHeightForSection:(NSInteger)section
{
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForHeaderInSection:)]) {
        self.headerHeight = [self.delegate collectionView:self.collectionView layout:self heightForHeaderInSection:section];
    }
    return self.headerHeight;
}
/**
 *  獲取段尾的高度
 */
-(CGFloat)footerHeightForSection:(NSInteger)section
{
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForFooterInSection:)]) {
        self.footerHeight = [self.delegate collectionView:self.collectionView layout:self heightForFooterInSection:section];
    }
    
    return self.footerHeight;
}
-(UIEdgeInsets)sectionInsetForSection:(NSInteger)section
{
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
        self.sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
    }
    return self.sectionInset;
}
-(CGFloat)minimumInteritemSpacingForSection:(NSInteger)section
{
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) {
        self.minimumInteritemSpacing = [self.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section];
    }
    return self.minimumInteritemSpacing;
}
-(CGFloat)minimumLineSpacingForSection:(NSInteger)section
{
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]) {
        self.minimumLineSpacing = [self.delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:section];
    }
    return self.minimumLineSpacing;
}
/**
 *  獲取每個cell的size
 */
-(CGSize)itemSizeForIndexPath:(NSIndexPath *)indexPath
{
    CGFloat itemWidth = ([UIScreen mainScreen].bounds.size.width-self.sectionInset.left-self.sectionInset.right-(self.columnCount-1)*self.minimumInteritemSpacing)/self.columnCount;
    
    self.itemSize = CGSizeMake(itemWidth, itemWidth);
    
    if ([self.delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {
        CGSize size = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
        
        self.itemSize = CGSizeMake(self.itemSize.width, floorf(size.height * self.itemSize.width / size.width));
    }
    return self.itemSize;
}
@end
  1. 那咱們該怎么用呢?很簡單帆赢,在collectionView的初始化的時候綁定該布局類小压,并實現(xiàn)- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath方法以返回cell的高度线梗。一般把cell的高度放入數(shù)組中。
    如果有透視圖的話场航,要實現(xiàn)- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath這個方法缠导。

至此,就完成了8攘 Fг臁!如果大家遇到什么問題孩饼,盡管提髓削,相互交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末镀娶,一起剝皮案震驚了整個濱河市立膛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梯码,老刑警劉巖宝泵,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異轩娶,居然都是意外死亡儿奶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門鳄抒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闯捎,“玉大人,你說我怎么就攤上這事许溅∪勘牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵贤重,是天一觀的道長茬祷。 經(jīng)常有香客問我,道長游桩,這世上最難降的妖魔是什么牲迫? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮借卧,結(jié)果婚禮上盹憎,老公的妹妹穿的比我還像新娘。我一直安慰自己铐刘,他們只是感情好陪每,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般檩禾。 火紅的嫁衣襯著肌膚如雪挂签。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天盼产,我揣著相機與錄音饵婆,去河邊找鬼。 笑死戏售,一個胖子當著我的面吹牛侨核,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播灌灾,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搓译,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锋喜?” 一聲冷哼從身側(cè)響起些己,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘿般,沒想到半個月后段标,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡炉奴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年怀樟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盆佣。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖械荷,靈堂內(nèi)的尸體忽然破棺而出共耍,到底是詐尸還是另有隱情,我是刑警寧澤吨瞎,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布痹兜,位于F島的核電站,受9級特大地震影響颤诀,放射性物質(zhì)發(fā)生泄漏字旭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一崖叫、第九天 我趴在偏房一處隱蔽的房頂上張望遗淳。 院中可真熱鬧,春花似錦心傀、人聲如沸屈暗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽养叛。三九已至种呐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弃甥,已是汗流浹背爽室。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淆攻,地道東北人阔墩。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像卜录,于是被迫代替她去往敵國和親戈擒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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