自定義瀑布流布局

(本文主要講一下現(xiàn)在比較流行的一種布局方式----瀑布流布局 如有寫的不好的地方 還請多多指正 感謝)

1晦墙、功能分析

如圖所示: 我們可以看到該布局中的每個元素有一個共同的特點就是等寬不等高. 而且當(dāng)一行排列完成之后在下一行進(jìn)行排列時都是從最短的那一列開始排,否則的話就會讓每一列的差距越來越大從而顯得非常不美觀.

2、實現(xiàn)思路
  • 根據(jù)需求 該頁面需要有滾動效果 而且可以展示很多數(shù)據(jù) 所以決定用UICollectionView來完成 那么UICollectionView中具體的每一個cell如何排列就是我們需要解決的問題了.也就是說我們需要計算出每一個cell的frame.
  • 接下來就對cell的x值,y值,寬度,高度進(jìn)行逐一計算
  • 寬度w
    我們可以很直觀的從圖中看出寬度w=(collectionView的寬度 - cell左邊的邊距 - cell右邊的邊距 - (總共的列數(shù) - 1) * 每一列之間的間距) / 總共的列數(shù)
  • 高度h
    高度h是根據(jù)具體項目中的模型本身的高度來決定
  • x,y值
    根據(jù)上圖可以發(fā)現(xiàn)每一列中所有cell的x值是一樣的 所以要算x值只需要求出列號就行了. y值就是最短的那一列的cell最大y值再加上間距
    綜上所述,現(xiàn)在需要做的首要任務(wù)就是找出最短的那一列.所以我們需要通過遍歷每一列的高度來找出最短的那一列.
3、code

以上進(jìn)行簡單分析之后就要開始動手了 既然是自定義布局 我們就需要創(chuàng)建一個繼承自UICollectionViewLayout的類來實現(xiàn)布局 在這個類中我們需要實現(xiàn)以下幾個方法

  • - (void)prepareLayout這個方法是用來進(jìn)行初始化的 實現(xiàn)代碼如下
-(void)prepareLayout
{
    [super prepareLayout];
    //清除之前計算的所有高度
    [self.colunmHeights removeAllObjects];
    for (NSInteger i = 0; i < ZDDefaultColumnCount; i++) {
        [self.colunmHeights addObject:@(ZDDefaultEdgeInsets.top)];
    }
    //清除之前所有的布局
    [self.attrsArray removeAllObjects];
    //創(chuàng)建每一個cell對應(yīng)的布局屬性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        //創(chuàng)建位置
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        //獲取indexPath位置cell對應(yīng)的布局屬性
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

其中self.colunmHeightsself.attrsArray是自己定義的兩個屬性 分別用來保存所有列的當(dāng)前高度以及所有cell的布局屬性(兩個都是可變數(shù)組類型)

  • - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect這個方法是用來決定cell的排布 實現(xiàn)代碼如下
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}
  • - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath這個方法是用來返回indexPath的位置所對應(yīng)的cell的布局屬性的 實現(xiàn)代碼如下
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //創(chuàng)建布局屬性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    //collectionView的寬度
    CGFloat collectionViewW = self.collectionView.frame.size.width;
    //設(shè)置布局屬性的frame
    CGFloat w = (collectionViewW - ZDDefaultEdgeInsets.left - ZDDefaultEdgeInsets.right - (ZDDefaultColumnCount - 1) * ZDDefaultColumnMargin) / ZDDefaultColumnCount;
    CGFloat h = 50 + arc4random_uniform(100);
    
    //找出高度最短的那一列
    NSInteger destColumn = 0;
    CGFloat minColumnHeight = [self.colunmHeights[0] doubleValue];
    for (NSInteger i = 1; i < ZDDefaultColumnCount; i++) {
        CGFloat columnHeight = [self.colunmHeights[i] doubleValue];
        
        if (minColumnHeight > columnHeight) {
            minColumnHeight = columnHeight;
            destColumn = i;
        }
    }
    CGFloat x = ZDDefaultEdgeInsets.left + destColumn * (w + ZDDefaultColumnMargin);
    CGFloat y = minColumnHeight;
    if (y != ZDDefaultEdgeInsets.top) {
        y += ZDDefaultRowMargin;
    }
    attrs.frame = CGRectMake(x, y, w, h);
    
    //更新最短那列的高度
    self.colunmHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
    
    // 記錄內(nèi)容的高度
    CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
    if (self.contentHeight < columnHeight) {
        self.contentHeight = columnHeight;
    }
    return attrs;
}

其中self.contentHeight是自定義的一個屬性 用來保存內(nèi)容的高度

  • - (CGSize)collectionViewContentSize這個方法是為了讓collectionView可以滾動起來 實現(xiàn)代碼如下
-(CGSize)collectionViewContentSize
{
    return CGSizeMake(0, self.contentHeight + ZDDefaultEdgeInsets.bottom);
}
4疮跑、優(yōu)化
  • 考慮到代碼的復(fù)用性 方便直接將代碼拖到下個項目中去
    (比如具體要展示多少列,每個cell之間的間距等等都需要根據(jù)具體的項目需求來決定) 所以對代碼進(jìn)行優(yōu)化
  • 優(yōu)化思路是根據(jù)UITableViewDelegate tableView具體展示什么數(shù)據(jù),展示多少數(shù)據(jù)都是由其數(shù)據(jù)源和代理方法來具體實現(xiàn)的,所以我也設(shè)計了一個代理屬性 具體顯示多少列 每個cell之間的間距都是有代理方法來實現(xiàn)的 具體實現(xiàn)代碼如下:
  • ZDWaterfallLayout.h文件中:
@class ZDWaterfallLayout;
@protocol ZDWaterfallLayoutDelegate <NSObject>
@required
-(CGFloat)waterfallLayout:(ZDWaterfallLayout *)waterfallLayout heightForItemAtIndex:(NSInteger)index itemWidth:(CGFloat)itemWidth;
@optional
//列數(shù)
-(CGFloat)columnCountInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
//每一列之間的間距
-(CGFloat)columnMarginInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
//每一行之間的間距
-(CGFloat)rowMarginInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
//cell的邊距
-(UIEdgeInsets)edgeInsetsInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout;
@end
@interface ZDWaterfallLayout : UICollectionViewLayout
/**布局代理屬性*/
@property (nonatomic,weak) id<ZDWaterfallLayoutDelegate>delegate ;
@end
  • ZDWaterfallLayout.m文件中
-(CGFloat)rowMargin
{
    if ([self.delegate respondsToSelector:@selector(rowMarginInWaterfallLayout:)]) {
        return [self.delegate rowMarginInWaterfallLayout:self];
    }else{
        return ZDDefaultRowMargin;
    }
}
-(CGFloat)columnMargin
{
    if ([self.delegate respondsToSelector:@selector(columnMarginInWaterfallLayout:)]) {
        return [self.delegate columnMarginInWaterfallLayout:self];
    }else{
        return ZDDefaultColumnMargin;
    }
}
-(NSInteger)columnCount
{
    if ([self.delegate respondsToSelector:@selector(columnCountInWaterfallLayout:)]) {
        return [self.delegate columnCountInWaterfallLayout:self];
    }else{
        return ZDDefaultColumnCount;
    }
}
-(UIEdgeInsets)edgeInsets
{
    if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterfallLayout:)]) {
        return [self.delegate edgeInsetsInWaterfallLayout:self];
    }else{
        return ZDDefaultEdgeInsets;
    }
}

ZDDefaultRowMargin ZDDefaultColumnMargin ZDDefaultColumnCount ZDDefaultEdgeInsets這是我自定義的默認(rèn)值
接下來想要改變布局效果就很簡單了 只需要通過具體實現(xiàn)幾個代理方法就可以搞定 比如我想排5列 只需要實現(xiàn)如下方法即可:

-(CGFloat)columnCountInWaterfallLayout:(ZDWaterfallLayout *)waterfallLayout
{
    return 5;
}

顯示效果如下:


就是這么輕松愉快~
具體demo請看我的github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胖喳,一起剝皮案震驚了整個濱河市以清,隨后出現(xiàn)的幾起案子儿普,更是在濱河造成了極大的恐慌,老刑警劉巖掷倔,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眉孩,死亡現(xiàn)場離奇詭異,居然都是意外死亡勒葱,警方通過查閱死者的電腦和手機(jī)浪汪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凛虽,“玉大人死遭,你說我怎么就攤上這事∩” “怎么了殃姓?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瓦阐。 經(jīng)常有香客問我蜗侈,道長,這世上最難降的妖魔是什么睡蟋? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任踏幻,我火速辦了婚禮,結(jié)果婚禮上戳杀,老公的妹妹穿的比我還像新娘该面。我一直安慰自己,他們只是感情好信卡,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布隔缀。 她就那樣靜靜地躺著,像睡著了一般傍菇。 火紅的嫁衣襯著肌膚如雪猾瘸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天丢习,我揣著相機(jī)與錄音牵触,去河邊找鬼。 笑死咐低,一個胖子當(dāng)著我的面吹牛揽思,可吹牛的內(nèi)容都是我干的见擦。 我是一名探鬼主播钉汗,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼羹令,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了损痰?” 一聲冷哼從身側(cè)響起特恬,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徐钠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體役首,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡尝丐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衡奥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爹袁。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矮固,靈堂內(nèi)的尸體忽然破棺而出失息,到底是詐尸還是另有隱情,我是刑警寧澤档址,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布盹兢,位于F島的核電站,受9級特大地震影響守伸,放射性物質(zhì)發(fā)生泄漏绎秒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一尼摹、第九天 我趴在偏房一處隱蔽的房頂上張望见芹。 院中可真熱鬧,春花似錦蠢涝、人聲如沸玄呛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽徘铝。三九已至,卻和暖如春儿咱,著一層夾襖步出監(jiān)牢的瞬間庭砍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工混埠, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留怠缸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓钳宪,卻偏偏與公主長得像揭北,于是被迫代替她去往敵國和親扳炬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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