詳解IOS中如何實現(xiàn)瀑布流效果
首先是效果演示
特點:可以自由設(shè)置瀑布流的總列數(shù)(效果演示為2列)
雖然iphone手機的系統(tǒng)相冊沒有使用這種布局效果,瀑布流依然是一種很常見的布局方式!!!下面來詳細(xì)介紹如何實現(xiàn)這種布局.
首先使用的類是UICollectionView
我們要做的是自定義UICollectionViewCell和UICollectionViewLayout
1、自定義UICollectionViewCell類,只需要一個UIImageView即可,frame占滿整個cell.
2、重點是自定義UICollectionViewLayout,注意一定要繼承于UICollectionViewLayout,千萬別繼承于UIColletionViewFlowLayout.
3、另外還需要計算圖片高度.
為什么要自定義UICollectionViewLayout ?
因為我們需要設(shè)置每個item的高度以及位置, 注意這里是位置, 我們真的會設(shè)置每個item的位置的相信我!!!自定義UICollectionViewLayout必須要重寫三個協(xié)議方法,后面會講到.
為什么要計算圖片高度 ?
因為圖片寬度固定,所以需要按照圖片的比例來計算高度,使圖片等比例顯示.這樣的好處是,媽媽再也不用擔(dān)心我的照片被拉伸的奇形怪狀了...而且還需要用圖片的高度來計算整個CollectionView的contentSize...打完收工!!!
主菜來了!!!
以下內(nèi)容均在自定義的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新特性。
通過上邊的三個方法名我們可以大致了解需要去做什么.說一下第二個方法,需要返回一個數(shù)組,數(shù)組存放布局屬性(UICollectionViewLayoutAttributes)對象.那么我們需要寫一個屬性數(shù)組(attributesArray),將布局屬性放入這個屬性數(shù)組并返回.
還需要什么呢 ?看了文章開頭的朋友應(yīng)該注意到了,設(shè)置瀑布流的列數(shù)當(dāng)然得有個屬性(numberOfColumns)來表示列數(shù).
請注意,因為要在外部設(shè)置列數(shù),所以這個屬性需要寫在自定義類的.h文件中
另外為了方便,定義一個屬性(itemWidth)來表示item的寬度
再定義一個屬性(contentHeight)來表示整個collectionView的contenView的高度
首先是初始化,并沒有什么問題
- (instancetype)init { self = [super init]; if (self) { _attributesArray = [NSMutableArray array]; // 默認(rèn)值設(shè)置為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]; // 設(shè)置列的初始高度為邊距的高度,沒毛病!!! for (int i = 0; i < _numberOfColumns; i++) { // 所有列初始高度均設(shè)置為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ù)組當(dāng)然是一開始定義的布局屬性數(shù)組了 [_attributesArray addObject:layoutAttributes]; // 設(shè)置每個item的位置(x, y, width, height) // 橫坐標(biāo)的起始位置 #pragma mark - 比如一共兩列,現(xiàn)在要放一張圖片上去,需要放到高度最小的那一列. #pragma mark - 假設(shè)第0列最短,那么item的x坐標(biāo)就是從一個邊距寬度那里開始. #pragma mark - (itemWidth + cellMargin)為一個整體 CGFloat x = (self.itemWidth + _cellMargin) * shortestColumn + _cellMargin; // 縱坐標(biāo)就是 總高度數(shù)組 中最小列對應(yīng)的高度 #pragma mark - 圖片始終是添加在高度最小的那一列 CGFloat y = [columnHeightArray[column] floatValue];/**<注意類型轉(zhuǎn)換 */ // 寬度沒什么好說的 CGFloat width = self.itemWidth; #pragma mark - 這里給自定義的類聲明了一個協(xié)議,通過協(xié)議得到圖片的高度,調(diào)用時機就是需要item高度的時候 #pragma mark - 將Item的寬度傳給代理人(ViewController),VC計算好高度后將高度返回給自定義類 #pragma mark - 也就是↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ CGFloat height = [self.delegate collectionView:self.collectionView layout:self width:self.itemWidth heightForItemAtIndexPath:indexPath]; // 大功告成,設(shè)置item的位置信息,沒什么好說 layoutAttributes.frame = CGRectMake(x, y, width, height); // 上邊廢了半天勁放了一個item上去了,總高度數(shù)組是不是該更新一下數(shù)據(jù)了 columnHeightArray[shortestColumn] = @([columnHeightArray[shortestColumn] floatValue] + height + _cellMargin); // 整個內(nèi)容的高度,通過比較得到較大值作為整個內(nèi)容的高度 self.contentHeight = MAX(self.contentHeight, [columnHeightArray[shortestColumn] floatValue]); // 剛才放了一個item上去,那么此時此刻哪一列的高度比較低呢 for (int i = 0; i < _numberOfColumns; i++) { //當(dāng)前列的高度(剛才添加item的那一列) CGFloat currentHeight = [columnHeightArray[shortestColumn] floatValue]; // 取出第i列中存放列高度 CGFloat height = [columnHeightArray[i] floatValue]; if (currentHeight > height) { //第i列高度(height)最低時,高度最低的列(shortestColumn)當(dāng)然就是第i列了 shortestColumn = i; } } } // 思考下只使用上邊的代碼會出現(xiàn)什么問題 // 并不能影響心情,請不要恐慌... // 提示:這個方法會被多次調(diào)用,數(shù)組 }
---難點已經(jīng)結(jié)束---
沒有理解的朋友請重點看上邊的難點方法.
必須重寫的第二個方法
// 2.返回的是, 每個item對應(yīng)的布局屬性 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { return _attributesArray; }
必須重寫的第三個方法
// 3.返回CollectionView的滾動范圍 - (CGSize)collectionViewContentSize { return CGSizeMake(0, _contentHeight); }
ViewController里邊關(guān)于CollectionView的創(chuàng)建和協(xié)議方法就沒什么好說的了.
看下自定義類CustomCollectionViewLayout的創(chuàng)建以及屬性的賦值情況:
CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] init]; layout.numberOfColumns = 2;/**< 在ViewController里設(shè)置瀑布流的列數(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ù)傳過來的寬度來設(shè)置一個合適的矩形, 高度設(shè)為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; }
總結(jié)
到這里呢,在IOS實現(xiàn)瀑布流就算是結(jié)束了,有興趣的朋友可以自己動手試一下,希望本文對大家開發(fā)IOS有所幫助。
相關(guān)文章
iOS App中調(diào)用相冊中圖片及獲取最近的一張圖片的方法
這篇文章主要介紹了iOS App中調(diào)用相冊中圖片及獲取最近的一張圖片的方法,示例代碼為傳統(tǒng)的Objective-C語言,需要的朋友可以參考下2016-03-03iOS開發(fā)實戰(zhàn)之Label全方位對齊的輕松實現(xiàn)
這篇文章主要給大家介紹了關(guān)于iOS開發(fā)實戰(zhàn)之輕松實現(xiàn)Label全方位對齊的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10IOS 開發(fā)之UILabel 或者 UIButton加下劃線鏈接
這篇文章主要介紹了IOS 開發(fā)之UILabel 或者 UIButton加下劃線鏈接的相關(guān)資料,需要的朋友可以參考下2017-07-07詳解iOS應(yīng)用的設(shè)計模式開發(fā)中Mediator中介者模式的使用
這篇文章主要介紹了iOS應(yīng)用的設(shè)計模式開發(fā)中Mediator中介者模式的使用,示例代碼為傳統(tǒng)的Objective-C語言,需要的朋友可以參考下2016-03-03iOS應(yīng)用中使用Auto Layout實現(xiàn)自定義cell及拖動回彈
這篇文章主要介紹了iOS應(yīng)用中使用Auto Layout實現(xiàn)自定義cell及拖動回彈的方法,自定義UITableViewCell并使用Auto Layout對其進(jìn)行約束可以方便地針對多尺寸屏幕進(jìn)行調(diào)整,代碼為Swift語言,需要的朋友可以參考下2016-03-03