iOS瀑布流詳細介紹

  • 傳統(tǒng)!!!依然是效果演示
瀑布流效果.gif
  • 特點:可以自由設置瀑布流的總列數(shù)(效果演示為2列)

雖然iphone手機的系統(tǒng)相冊沒有使用這種布局效果,瀑布流依然是一種很常見的布局方式!!!下面來詳細介紹如何實現(xiàn)這種布局.

  • 首先使用的類是UICollectionView
  • 我們要做的是自定義UICollectionViewCell和UICollectionViewLayout
  1. 自定義UICollectionViewCell類,只需要一個UIImageView即可,frame占滿整個cell.
  1. 重點是自定義UICollectionViewLayout,注意一定要繼承于UICollectionViewLayout,千萬別繼承于UIColletionViewFlowLayout.
  2. 另外還需要計算圖片高度.
  • 為什么要自定義UICollectionViewLayout ?

因為我們需要設置每個item的高度以及位置, 注意這里是位置, 我們真的會設置每個item的位置的相信我!!!自定義UICollectionViewLayout必須要重寫三個協(xié)議方法,后面會講到.

  • 為什么要計算圖片高度 ?

因為圖片寬度固定,所以需要按照圖片的比例來計算高度,使圖片等比例顯示.這樣的好處是,媽媽再也不用擔心我的照片被拉伸的奇形怪狀了...而且還需要用圖片的高度來計算整個CollectionView的contentSize...打完收工!!!

  • 主菜來了!!!

以下內容均在自定義的CustomCollectionViewLayout類里邊

//自定義UICollectionViewLayout必須要重寫的三個協(xié)議方法
//1.計算每個item的大小和位置
- (void)prepareLayout;
//2.返回每個item的布局屬性
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
//3.返回collectionView的總高度
- (CGSize)collectionViewContentSize;

可以看到第三個方法使用了Nullability和泛型,系統(tǒng)的方法都添加了iOS 9新特性,不了解的朋友可以點擊這里來查看iOS 9新特性的說明.

  • 通過上邊的三個方法名我們可以大致了解需要去做什么.說一下第二個方法,需要返回一個數(shù)組,數(shù)組存放布局屬性(UICollectionViewLayoutAttributes)對象.那么我們需要寫一個屬性數(shù)組(attributesArray),將布局屬性放入這個屬性數(shù)組并返回.
定義存放布局屬性的可變數(shù)組
  • 還需要什么呢 ?看了文章開頭的朋友應該注意到了,設置瀑布流的列數(shù)當然得有個屬性(numberOfColumns)來表示列數(shù).
請注意,因為要在外部設置列數(shù),所以這個屬性需要寫在自定義類的.h文件中
  • 另外為了方便,定義一個屬性(itemWidth)來表示item的寬度
  • 再定義一個屬性(contentHeight)來表示整個collectionView的contenView的高度
CustomCollectionViewLayout.m
  • 首先是初始化,并沒有什么問題
- (instancetype)init {
    self = [super init];
    if (self) {
        _attributesArray = [NSMutableArray array];
        // 默認值設置為2列
        _numberOfColumns = 2;
        _contentHeight = 0.0f;
        _cellMargin = 5.0f;/**< 用來表示間距的屬性 */
    }
    return self;
}
  • 然后是getter方法,只需要使用點語法即可得到itemWidth的值(因為它就是固定的)
- (CGFloat)itemWidth {
    //所有邊距的和.兩列時有三個邊距, 三列時有四個邊距,邏輯強大就是好...
    CGFloat allMargin = (_numberOfColumns + 1) * _cellMargin;
    //除去邊界之后的總寬度
    CGFloat noMarginWidth = CGRectGetWidth(self.collectionView.bounds) - allMargin;
    //出去邊距的總寬度除以列數(shù)得到每一列的寬度(也就是itemWidth)
    return noMarginWidth / _numberOfColumns;
}

---接下來是難點---

  • 必須重寫的第一個方法
- (void)prepareLayout {
    // 定義變量記錄高度最小的列,初始為第0列高度最小.
#pragma mark - 注意這個是從0開始算的啊!!!
    NSInteger shortestColumn = 0;
#pragma mark - 注意這個是從0開始算的啊!!!
    // 存儲每一列的總高度.因為添加圖片的列高度會變,所以需要定義一個數(shù)組來記錄列的總高度.
    NSMutableArray *columnHeightArray = [NSMutableArray array];
    // 設置列的初始高度為邊距的高度,沒毛病!!!
    for (int i = 0; i < _numberOfColumns; i++) {
        // 所有列初始高度均設置為cell的間距
        [columnHeightArray addObject:@(_cellMargin)];
    }
    // 遍歷collectionView中第 0 區(qū)中的所有item
    for (int i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) {
        //需要用到這個玩意,提前拿到.
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 創(chuàng)建系統(tǒng)需要的布局屬性對象,看后邊的參數(shù)就知道這就是每個item的布局屬性了
        UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
        // 將布局屬性放入數(shù)組中,這個數(shù)組當然是一開始定義的布局屬性數(shù)組了
        [_attributesArray addObject:layoutAttributes];
        // 設置每個item的位置(x, y, width, height)
        // 橫坐標的起始位置
#pragma mark - 比如一共兩列,現(xiàn)在要放一張圖片上去,需要放到高度最小的那一列.
#pragma mark - 假設第0列最短,那么item的x坐標就是從一個邊距寬度那里開始.
#pragma mark - (itemWidth + cellMargin)為一個整體
        CGFloat x = (self.itemWidth + _cellMargin) * shortestColumn + _cellMargin;
        // 縱坐標就是 總高度數(shù)組 中最小列對應的高度
#pragma mark - 圖片始終是添加在高度最小的那一列
        CGFloat y = [columnHeightArray[shortestColumn] floatValue];/**<注意類型轉換 */
        // 寬度沒什么好說的
        CGFloat width = self.itemWidth;
#pragma mark - 這里給自定義的類聲明了一個協(xié)議,通過協(xié)議得到圖片的高度,調用時機就是需要item高度的時候
#pragma mark - 將Item的寬度傳給代理人(ViewController),VC計算好高度后將高度返回給自定義類
#pragma mark - 也就是↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        CGFloat height = [self.delegate collectionView:self.collectionView
                                                layout:self
                                                 width:self.itemWidth
                              heightForItemAtIndexPath:indexPath];
        // 大功告成,設置item的位置信息,沒什么好說
        layoutAttributes.frame = CGRectMake(x, y, width, height);
        // 上邊廢了半天勁放了一個item上去了,總高度數(shù)組是不是該更新一下數(shù)據(jù)了
        columnHeightArray[shortestColumn] = @([columnHeightArray[shortestColumn] floatValue] + height + _cellMargin);
        // 整個內容的高度,通過比較得到較大值作為整個內容的高度
        self.contentHeight = MAX(self.contentHeight, [columnHeightArray[shortestColumn] floatValue]);
        // 剛才放了一個item上去,那么此時此刻哪一列的高度比較低呢
        for (int i = 0; i < _numberOfColumns; i++) {
            //當前列的高度(剛才添加item的那一列)
            CGFloat currentHeight = [columnHeightArray[shortestColumn] floatValue];
            // 取出第i列中存放列高度
            CGFloat height = [columnHeightArray[i] floatValue];
            if (currentHeight > height) {
                //第i列高度(height)最低時,高度最低的列(shortestColumn)當然就是第i列了
                shortestColumn = i;
            }
        }
    }
// 思考下只使用上邊的代碼會出現(xiàn)什么問題
// 并不能影響心情,請不要恐慌...
// 提示:這個方法會被多次調用,數(shù)組
}

---難點已經結束---

沒有理解的朋友請重點看上邊的難點方法.

  • 必須重寫的第二個方法
// 2.返回的是, 每個item對應的布局屬性
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return _attributesArray;
}
  • 必須重寫的第三個方法
// 3.返回CollectionView的滾動范圍
- (CGSize)collectionViewContentSize {
    return CGSizeMake(0, _contentHeight);
}
  • ViewController里邊關于CollectionView的創(chuàng)建和協(xié)議方法就沒什么好說的了.
  • 看下自定義類CustomCollectionViewLayout的創(chuàng)建以及屬性的賦值情況:
CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] init];
layout.numberOfColumns = 2;/**< 在ViewController里設置瀑布流的列數(shù),2列或3列為最佳 */
layout.delegate = self;/**< 指定VC為計算高度協(xié)議方法的代理人 */
  • 再看下協(xié)議方法的實現(xiàn)部分(在ViewController.m中實現(xiàn))
- (CGFloat)collectionView:(UICollectionView *)collectionView
                   layout:(UICollectionViewLayout *)collectionViewLayout
                    width:(CGFloat)width
 heightForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
    UIImage *image = _imagesArray[indexPath.row];
    // 根據(jù)傳過來的寬度來設置一個合適的矩形, 高度設為CGFLOAT_MAX表示以寬度來計算高度
    CGRect boundingRect = CGRectMake(0, 0, width, CGFLOAT_MAX);
    // 通過系統(tǒng)函數(shù)來得到最終的矩形,需要引入頭文件
    // #import <AVFoundation/AVFoundation.h>
    CGRect imageCurrentRect = AVMakeRectWithAspectRatioInsideRect(image.size, boundingRect);
    return imageCurrentRect.size.height;
}

到這里呢,瀑布流就算是結束了,有興趣的朋友可以自己動手試一下.

沒明白的話可以在評論區(qū)提問

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末遏乔,一起剝皮案震驚了整個濱河市疟羹,隨后出現(xiàn)的幾起案子燎字,更是在濱河造成了極大的恐慌曲梗,老刑警劉巖月腋,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機胎源,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屿脐,“玉大人涕蚤,你說我怎么就攤上這事〉乃校” “怎么了万栅?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長西疤。 經常有香客問我烦粒,道長,這世上最難降的妖魔是什么代赁? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任扰她,我火速辦了婚禮,結果婚禮上芭碍,老公的妹妹穿的比我還像新娘徒役。我一直安慰自己,他們只是感情好豁跑,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泻云,像睡著了一般艇拍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宠纯,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天卸夕,我揣著相機與錄音,去河邊找鬼婆瓜。 笑死快集,一個胖子當著我的面吹牛贡羔,可吹牛的內容都是我干的。 我是一名探鬼主播个初,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼乖寒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了院溺?” 一聲冷哼從身側響起楣嘁,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎珍逸,沒想到半個月后逐虚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡谆膳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年叭爱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漱病。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡买雾,死狀恐怖,靈堂內的尸體忽然破棺而出缨称,到底是詐尸還是另有隱情凝果,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布睦尽,位于F島的核電站器净,受9級特大地震影響,放射性物質發(fā)生泄漏当凡。R本人自食惡果不足惜山害,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沿量。 院中可真熱鬧浪慌,春花似錦、人聲如沸朴则。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乌妒。三九已至汹想,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撤蚊,已是汗流浹背古掏。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侦啸,地道東北人槽唾。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓丧枪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庞萍。 傳聞我的和親對象是個殘疾皇子拧烦,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容

  • 翻譯自“Collection View Programming Guide for iOS” 0 關于iOS集合視...
    lakerszhy閱讀 3,837評論 1 22
  • 序言 前段時間開發(fā)的時候,需要在tableView上拉的時候實現(xiàn)最底下的cell隨著滑動從左邊移動出來的效果(淘寶...
    sindri的小巢閱讀 10,569評論 18 38
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案挂绰? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,734評論 1 92
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫屎篱、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,059評論 4 62
  • 原文鏈接:http://www.yinwang.org/blog-cn/2016/01/18/java/?from...
    lioilwin閱讀 246評論 0 1