你好,歡迎來到IOS教程網

 Ios教程網 >> IOS教程 >> 關於IOS教程 >> 詳解IOS中如何實現瀑布流效果

詳解IOS中如何實現瀑布流效果

編輯:關於IOS教程

首先是效果演示

特點:可以自由設置瀑布流的總列數(效果演示為2列)

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

首先使用的類是UICollectionView

我們要做的是自定義UICollectionViewCell和UICollectionViewLayout

     1、自定義UICollectionViewCell類,只需要一個UIImageView即可,frame占滿整個cell.

     2、重點是自定義UICollectionViewLayout,注意一定要繼承於UICollectionViewLayout,千萬別繼承於UIColletionViewFlowLayout.

     3、另外還需要計算圖片高度.

為什麼要自定義UICollectionViewLayout ?

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

為什麼要計算圖片高度 ?

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

主菜來了!!!

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

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

可以看到第三個方法使用了Nullability和泛型,系統的方法都添加了iOS 9新特性。

通過上邊的三個方法名我們可以大致了解需要去做什麼.說一下第二個方法,需要返回一個數組,數組存放布局屬性(UICollectionViewLayoutAttributes)對象.那麼我們需要寫一個屬性數組(attributesArray),將布局屬性放入這個屬性數組並返回.

還需要什麼呢 ?看了文章開頭的朋友應該注意到了,設置瀑布流的列數當然得有個屬性(numberOfColumns)來表示列數.

請注意,因為要在外部設置列數,所以這個屬性需要寫在自定義類的.h文件中

另外為了方便,定義一個屬性(itemWidth)來表示item的寬度

再定義一個屬性(contentHeight)來表示整個collectionView的contenView的高度

首先是初始化,並沒有什麼問題

- (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;
  //出去邊距的總寬度除以列數得到每一列的寬度(也就是itemWidth)
  return noMarginWidth / _numberOfColumns;
}

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

必須重寫的第一個方法

- (void)prepareLayout {
  // 定義變量記錄高度最小的列,初始為第0列高度最小.
#pragma mark - 注意這個是從0開始算的啊!!!
  NSInteger shortestColumn = 0;
#pragma mark - 注意這個是從0開始算的啊!!!
  // 存儲每一列的總高度.因為添加圖片的列高度會變,所以需要定義一個數組來記錄列的總高度.
  NSMutableArray *columnHeightArray = [NSMutableArray array];
  // 設置列的初始高度為邊距的高度,沒毛病!!!
  for (int i = 0; i < _numberOfColumns; i++) {
    // 所有列初始高度均設置為cell的間距
    [columnHeightArray addObject:@(_cellMargin)];
  }
  // 遍歷collectionView中第 0 區中的所有item
  for (int i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) {
    //需要用到這個玩意,提前拿到.
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
    // 創建系統需要的布局屬性對象,看後邊的參數就知道這就是每個item的布局屬性了
    UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
    // 將布局屬性放入數組中,這個數組當然是一開始定義的布局屬性數組了
    [_attributesArray addObject:layoutAttributes];
    // 設置每個item的位置(x, y, width, height)
    // 橫坐標的起始位置
#pragma mark - 比如一共兩列,現在要放一張圖片上去,需要放到高度最小的那一列.
#pragma mark - 假設第0列最短,那麼item的x坐標就是從一個邊距寬度那裡開始.
#pragma mark - (itemWidth + cellMargin)為一個整體
    CGFloat x = (self.itemWidth + _cellMargin) * shortestColumn + _cellMargin;
    // 縱坐標就是 總高度數組 中最小列對應的高度
#pragma mark - 圖片始終是添加在高度最小的那一列
    CGFloat y = [columnHeightArray[column] floatValue];/**<注意類型轉換 */
    // 寬度沒什麼好說的
    CGFloat width = self.itemWidth;
#pragma mark - 這裡給自定義的類聲明了一個協議,通過協議得到圖片的高度,調用時機就是需要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上去了,總高度數組是不是該更新一下數據了
    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;
      }
    }
  }
// 思考下只使用上邊的代碼會出現什麼問題
// 並不能影響心情,請不要恐慌...
// 提示:這個方法會被多次調用,數組
}

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

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

必須重寫的第二個方法

// 2.返回的是, 每個item對應的布局屬性
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
  return _attributesArray;
}

必須重寫的第三個方法

// 3.返回CollectionView的滾動范圍
- (CGSize)collectionViewContentSize {
  return CGSizeMake(0, _contentHeight);
}

ViewController裡邊關於CollectionView的創建和協議方法就沒什麼好說的了.

看下自定義類CustomCollectionViewLayout的創建以及屬性的賦值情況:

CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] init];
layout.numberOfColumns = 2;/**< 在ViewController裡設置瀑布流的列數,2列或3列為最佳 */
layout.delegate = self;/**< 指定VC為計算高度協議方法的代理人 */

再看下協議方法的實現部分(在ViewController.m中實現)

- (CGFloat)collectionView:(UICollectionView *)collectionView
          layout:(UICollectionViewLayout *)collectionViewLayout
          width:(CGFloat)width
 heightForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
  UIImage *image = _imagesArray[indexPath.row];
  // 根據傳過來的寬度來設置一個合適的矩形, 高度設為CGFLOAT_MAX表示以寬度來計算高度
  CGRect boundingRect = CGRectMake(0, 0, width, CGFLOAT_MAX);
  // 通過系統函數來得到最終的矩形,需要引入頭文件
  // #import <AVFoundation/AVFoundation.h>
  CGRect imageCurrentRect = AVMakeRectWithAspectRatioInsideRect(image.size, boundingRect);
  return imageCurrentRect.size.height;
}

總結

到這裡呢,在IOS實現瀑布流就算是結束了,有興趣的朋友可以自己動手試一下,希望本文對大家開發IOS有所幫助。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved