iOS瀑布流之橫-縱瀑布流

一神年、開(kāi)篇

1.瀑布流設(shè)計(jì)討論

1.應(yīng)用場(chǎng)景:在我們app開(kāi)發(fā)中,瀑布流一般應(yīng)用于大數(shù)據(jù)展示時(shí)知给,比如淘寶搜索頁(yè)面、蘑菇街app描姚、各大直播app主播列表頁(yè)面等等涩赢。

2.設(shè)計(jì)思路:我們首要考慮UICollectionView,因?yàn)閁ICollectionView特殊性和可復(fù)用性。因?yàn)槠俨剂髅總€(gè)格子item大小不同轩勘,需要計(jì)算每個(gè)item的寬高筒扒,就需要自定義UICollectionViewLayout。

2.瀑布流分類

我把瀑布流分為兩種:垂直瀑布流绊寻、水平瀑布流花墩。

1.垂直瀑布流:在我們app上實(shí)現(xiàn)的瀑布流一般是垂直瀑布流,也就是列數(shù)可設(shè)定澄步,item寬度由列數(shù)決定观游,從上往下布局。

2.水平瀑布流:我在搜索網(wǎng)頁(yè)百度圖片時(shí)驮俗,看到百度圖片的布局從而想到的一種布局方式。每行的高度是可設(shè)定的允跑,item高度由圖片大小決定王凑,比例縮放。

3.瀑布流樣式展示

垂直瀑布.jpeg

水平瀑布.jpeg

二聋丝、具體實(shí)現(xiàn)

1.創(chuàng)建一個(gè)UICollectionViewCell索烹,它上面只有一個(gè)imageView。

// CollectionViewCell.m
- (instancetype)initWithFrame:(CGRect)frame {
    
    if (self = [super initWithFrame:frame]) {
        _imageView = [[UIImageView alloc] init];
        _imageView.frame = self.bounds;
        [self addSubview:_imageView];
    }
    return self;
}

2.我們需要自定義一個(gè)layout弱睦,并且暴露一些可設(shè)置的屬性用來(lái)控制瀑布流的對(duì)應(yīng)展示百姓,讓瀑布流可用性更強(qiáng)大一些。

2.1 頭文件:DYTWaterflowLayout.h
@interface DYTWaterflowLayout : UICollectionViewLayout
/**
 *  行高(水平瀑布流時(shí)),默認(rèn)為100
 */
@property (nonatomic, assign) CGFloat rowHeight;
/**
 *  單元格寬度(垂直瀑布流時(shí))
 */
@property (nonatomic, assign, readonly) CGFloat itemWidth;
/**
 *  列數(shù) : 默認(rèn)為3
 */
@property (nonatomic, assign) NSInteger numberOfColumns;

/**
 *  內(nèi)邊距 : 每一列之間的間距 (top, left, bottom, right)默認(rèn)為{10, 10, 10, 10};
 */
@property (nonatomic, assign) UIEdgeInsets insets;

/**
 *  每一行之間的間距 : 默認(rèn)為10
 */
@property (nonatomic, assign) CGFloat rowGap;

/**
 *  每一列之間的間距 : 默認(rèn)為10
 */
@property (nonatomic, assign) CGFloat columnGap;

/**
 *  高度數(shù)組 : 存儲(chǔ)所有item的高度
 */
@property (nonatomic, strong) NSArray *itemHeights;

/**
 *  寬度數(shù)組 : 存儲(chǔ)所有item的寬度
 */
@property (nonatomic, strong) NSArray *itemWidths;

/**
 *  瀑布流類型 : 分為水平瀑布流 和 垂直瀑布流
 */
@property (nonatomic, assign) DirectionType type;

@end
2.2 .m文件:DYTWaterflowLayout.m屬性聲明
@interface DYTWaterflowLayout()

@property (nonatomic, strong) NSMutableArray *itemAttributes; // 存放每個(gè)cell的布局屬性

// 垂直瀑布流相關(guān)屬性
@property (nonatomic, strong) NSMutableArray *columnsHeights; // 每一列的高度(count=多少列)
@property (nonatomic, assign) CGFloat maxHeight; // 最長(zhǎng)列的高度(最大高度)
@property (nonatomic, assign) CGFloat minHeight; // 最短列的高度(最低高度)
@property (nonatomic, assign) NSInteger minIndex; // 最短列的下標(biāo)
@property (nonatomic, assign) NSInteger maxIndex; // 最長(zhǎng)列的下標(biāo)

// 水平瀑布流相關(guān)屬性
@property (nonatomic, strong) NSMutableArray *columnsWidths; // 每一行的寬度(count不確定)
@property (nonatomic, assign) NSInteger tempItemX; // 臨時(shí)x : 用來(lái)計(jì)算每個(gè)cell的x值
@property (nonatomic, assign) NSInteger maxRowIndex; //最大行

@end
2.3 .m文件:DYTWaterflowLayout.m主要方法實(shí)現(xiàn)
// 
#pragma mark -- 系統(tǒng)內(nèi)部方法
/**
 *  重寫父類布局
 */
- (void)prepareLayout {
    
    [super prepareLayout];
    // (水平瀑布流時(shí))重置最大行
    if ((self.type == HorizontalType)) {
        self.maxRowIndex = 0;
    }
    
    if (self.type == VerticalType) {
        // (垂直瀑布流時(shí))重置每一列的高度
        [self.columnsHeights removeAllObjects];
        for (NSUInteger i = 0; i < self.numberOfColumns; i++) {
            [self.columnsHeights addObject:@(self.insets.top)];
        }
    }
    
    // 計(jì)算所有cell的布局屬性
    [self.itemAttributes removeAllObjects];
    NSUInteger itemCount = [self.collectionView numberOfItemsInSection:0];
    self.tempItemX = self.insets.left;
    for (NSUInteger i = 0; i < itemCount; ++i) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        if (self.type == VerticalType) {
            [self setVerticalFrame:indexPath];
        }else if ((self.type == HorizontalType)){
            [self setHorizontalFrame:indexPath];
        }
    }
}

/**
 *  水平瀑布:設(shè)置每一個(gè)attrs的frame况木,并加入數(shù)組中
 */
- (void)setHorizontalFrame:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    CGFloat w = [self.itemWidths[indexPath.item] floatValue];
    CGFloat width = w + self.columnGap;
    CGFloat h = (self.rowHeight == 0) ? 100 : self.rowHeight;
    
    /**
     *  如果當(dāng)前的x值+當(dāng)前cell的寬度 超出了 屏幕寬度垒拢,那么就要換行了。
     *  換行操作 : 最大行+1火惊,tempItemX重置為10(self.insets.left)求类。
     */
    if (self.tempItemX + w > [UIScreen mainScreen].bounds.size.width) {
        self.maxRowIndex++;
        self.tempItemX = self.insets.left;
    }
    CGFloat x = self.tempItemX;
    CGFloat y = self.insets.top + self.maxRowIndex * (h + self.rowGap);
    attrs.frame = CGRectMake(x, y, w, h);
    
    /**
     * 注:1.cell的寬度和高度算起來(lái)比較簡(jiǎn)單 : 寬度由外部傳進(jìn)來(lái),高度固定為rowHeight(默認(rèn)為100)屹耐。
     *    2.cell的x : 通過(guò)tempItemX算好了尸疆。
     *    3.cell的y : minHeight最短列的高度,也就是最低高度,作為當(dāng)前cell的起始y寿弱,當(dāng)然要加上行之間的間隙犯眠。
     */
    
    NSLog(@"%@",NSStringFromCGRect(attrs.frame));
    [self.itemAttributes addObject:attrs];
    self.tempItemX += width;
}

/**
 *  垂直瀑布:設(shè)置每一個(gè)attrs的frame,并加入數(shù)組中
 */
- (void)setVerticalFrame:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    // cell的frame
    CGFloat w = self.itemWidth;
    CGFloat h = [self.itemHeights[indexPath.item] floatValue];
    CGFloat x = self.insets.left + self.minIndex * (w + self.columnGap);
    CGFloat y = self.minHeight + self.rowGap;
    attrs.frame = CGRectMake(x, y, w, h);
    
    /**
     * 注:1.cell的寬度和高度算起來(lái)比較簡(jiǎn)單 : 寬度固定(itemWidth已經(jīng)算好)症革,高度由外部傳進(jìn)來(lái)
     *    2.cell的x : minIndex最短列作為當(dāng)前列筐咧。
     *    3.cell的y : minHeight最短列的高度,也就是最低高度地沮,作為當(dāng)前cell的起始y嗜浮,當(dāng)然要加上行之間的間隙。
     */
    
    // 更新數(shù)組中的最大高度
    self.columnsHeights[self.minIndex] = @(CGRectGetMaxY(attrs.frame));
    NSLog(@"%@",NSStringFromCGRect(attrs.frame));
    [self.itemAttributes addObject:attrs];
}

/**
 *  返回collectionView的尺寸
 */
- (CGSize)collectionViewContentSize {
    CGFloat height;
    if (self.type == HorizontalType) {
        CGFloat rowHeight = (self.rowHeight == 0) ? 100 : self.rowHeight;
        height = self.insets.top + (self.maxRowIndex+1) * (rowHeight + self.rowGap);
    }else {
        height = self.maxHeight;
    }
    return CGSizeMake(self.collectionView.frame.size.width, height);
}

/**
 *  所有元素(比如cell摩疑、補(bǔ)充控件危融、裝飾控件)的布局屬性
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.itemAttributes;
}
3.1 直接在控制器里使用
    // 設(shè)置布局
    DYTWaterflowLayout *layout = [[DYTWaterflowLayout alloc]init];
    layout.type = _type;
    // 設(shè)置相關(guān)屬性(不設(shè)置的話也行,都有相關(guān)默認(rèn)配置)
    layout.numberOfColumns = 3;
    layout.columnGap = 10;
    layout.rowGap = 10;
    layout.insets = UIEdgeInsetsMake(10, 10, 10, 10);
    layout.rowHeight = 100;
    self.collectionView.collectionViewLayout = self.waterflowLayout = layout;
3.2 (舉例)垂直瀑布流時(shí)雷袋,SDWebImage獲取圖片block里的具體實(shí)現(xiàn)
if (weakSelf.heights.count < weakSelf.allImageUrls.count) {
   // 根據(jù)圖片原始比例 計(jì)算 當(dāng)前圖片的高度(寬度固定)
   CGFloat scale = image.size.height / image.size.width;
   CGFloat width = weakSelf.waterflowLayout.itemWidth;
   CGFloat height = width * scale;
   NSNumber *heightNum = [NSNumber numberWithFloat:height];
   [weakSelf.heights addObject:heightNum];
}
if (weakSelf.heights.count == weakSelf.allImageUrls.count) {
   // 賦值所有cell的高度數(shù)組itemHeights
   weakSelf.waterflowLayout.itemHeights = weakSelf.heights;
   [weakSelf.collectionView reloadData];
}
3.3 UICollectionViewDataSource中要注意的點(diǎn)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    cell.imageView.image = self.picImageArr[indexPath.item];
    // 注:非常關(guān)鍵的一句吉殃,由于cell的復(fù)用,imageView的frame可能和cell對(duì)不上楷怒,需要重新設(shè)置蛋勺。
    cell.imageView.frame = cell.bounds;
    cell.backgroundColor = [UIColor orangeColor];
    return cell;
}

三、總結(jié)

1.瀑布流使用場(chǎng)景比較廣泛鸠删,也是常用的技術(shù)之一抱完,我也是又回顧了一遍,并且總結(jié)了整體的思路決定分享出來(lái)刃泡,結(jié)尾有demo巧娱,童鞋們可以自行下載。另外我參考的資料鏈接也會(huì)貼出烘贴,供大家研究比對(duì)禁添。
2.水平瀑布流還沒(méi)達(dá)到百度圖片搜索的那種效果,右邊距離屏幕間隙太大了桨踪,所以影響美觀老翘。后續(xù)會(huì)繼續(xù)研究,期待有所突破锻离。
3.有種情況是后臺(tái)直接給圖片的所有數(shù)據(jù)給我們铺峭,包括url、圖片寬高等等纳账,其實(shí)這樣就是后臺(tái)已經(jīng)做好了圖片的順序優(yōu)化處理逛薇。不過(guò)我們可以自己研究一下這個(gè)排序思路。如何達(dá)到右邊間隙幾乎相同疏虫,比如都為10永罚。
4.當(dāng)然有疑惑的地方可以留言或者直接私信我啤呼,我們可以一起討論。

四呢袱、總結(jié)最終實(shí)現(xiàn)效果:

瀑布流.gif

本文章demo:
瀑布流Demo
參考相關(guān)文章:
iOS--瀑布流的實(shí)現(xiàn) -- 作者Go_Spec
iOS 瀑布流基本實(shí)現(xiàn) -- 作者iOS_成才錄

額官扣。。羞福。如果想知道圖片里的小姐姐是誰(shuí)惕蹄,請(qǐng)直接在文章下面留言。因?yàn)槲蚁霑簳r(shí)留點(diǎn)懸念給大家治专。(皮一下就很開(kāi)心??)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卖陵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子张峰,更是在濱河造成了極大的恐慌泪蔫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喘批,死亡現(xiàn)場(chǎng)離奇詭異撩荣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)饶深,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門餐曹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人敌厘,你說(shuō)我怎么就攤上這事台猴。” “怎么了俱两?”我有些...
    開(kāi)封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵卿吐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我锋华,道長(zhǎng),這世上最難降的妖魔是什么箭窜? 我笑而不...
    開(kāi)封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任毯焕,我火速辦了婚禮,結(jié)果婚禮上磺樱,老公的妹妹穿的比我還像新娘纳猫。我一直安慰自己,他們只是感情好竹捉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布芜辕。 她就那樣靜靜地躺著,像睡著了一般块差。 火紅的嫁衣襯著肌膚如雪侵续。 梳的紋絲不亂的頭發(fā)上倔丈,一...
    開(kāi)封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音状蜗,去河邊找鬼需五。 笑死,一個(gè)胖子當(dāng)著我的面吹牛轧坎,可吹牛的內(nèi)容都是我干的宏邮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缸血,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜜氨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捎泻,我...
    開(kāi)封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤飒炎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后族扰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體厌丑,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渔呵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怒竿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扩氢,死狀恐怖耕驰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情录豺,我是刑警寧澤朦肘,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站双饥,受9級(jí)特大地震影響媒抠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咏花,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一趴生、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昏翰,春花似錦苍匆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至统求,卻和暖如春检碗,著一層夾襖步出監(jiān)牢的瞬間据块,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工后裸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瑰钮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓微驶,卻偏偏與公主長(zhǎng)得像浪谴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子因苹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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

  • 歷時(shí)幾天,“油膩的中年”這個(gè)熱點(diǎn)應(yīng)該已經(jīng)過(guò)去了款筑。不是我清高智蝠,是今天才有時(shí)間靜下心來(lái)寫這個(gè)選題。 追熱點(diǎn)滯后也不全是...
    水晶陪你閱讀 832評(píng)論 1 8
  • 愛(ài)奈梳,是一種追求杈湾。同時(shí),愛(ài)更是一種能力攘须,一種是否能給予別人愛(ài)漆撞,而自己內(nèi)心也充盈著愛(ài)和喜悅的能力∮谥妫“利他愛(ài)己浮驳,...
    薩希兒的風(fēng)閱讀 1,172評(píng)論 0 1
  • 肯定法:1.今天早上生活老師發(fā)消息叫寶貝起床去早鍛煉,寶貝就自己起來(lái)了捞魁,已經(jīng)持續(xù)兩個(gè)星期了至会,我說(shuō):寶貝你從一開(kāi)始要...
  • 我曾經(jīng)說(shuō)過(guò),我不是一個(gè)真正的吃貨谱俭。為什么呢奋献? 其一,我對(duì)美食沒(méi)有特別的渴求旺上。真正的吃貨應(yīng)該是一有機(jī)會(huì)就全城搜索美食...
    艾普蘿妮閱讀 223評(píng)論 0 2