1. 程式人生 > >iOS開發-進階:瀑布流基本實現

iOS開發-進階:瀑布流基本實現

文章轉自: http://www.jianshu.com/p/78830bdb04a9

一、瀑布流設計方案


不可取.png
過於複雜.png
最優方案.png

二、瀑布流設計思路分析

  • 1、自定義流水佈局中,指定滾動方向、預設列數、行間距、列間距、以及指定cell的大小itemSize
  • 2、可以提供一個數組columnMaxYs(記錄當前每一列的最大Y值),假如3列,我們就提供一個3個元素的陣列,記錄所有佈局屬性 

      1. columnMaxYs實現懶載入,
    • 2.並在prepareLayout方法中 : 
      • 1.先將columnMaxYs清空。
      • 2.再進行初始化每個元素為0. 
      • 3.獲取所有的Cell的佈局屬性,而每一個Cell的佈局屬性通過呼叫layoutAttributesForItemAtIndexPath:方式獲取,而呼叫該方法,就會執行第4步
        (至於為什麼在prepareLayout方法中初始化,而不是在init
        方法初始化: 是因為,該方法每次重新整理都會呼叫,而init方法中只會在建立佈局物件的時候只執行一次, 例如:如果進行下拉重新整理最新資料的時候,需求重新初始化資料,而如果我們使用的是init方法的話,並不會呼叫也就並不會清除之前的然後初始化, 而使用該方法prepareLayout可以辦到,因為它會再次呼叫,而init不會呼叫)
      • 3.獲取所有Cell的佈局屬性,
         注意:對與所有Cell的佈局屬性,在第一次載入的時候需要計算一次,而當重新整理collectionView的時候,當然也需要重新計算:所以我們提供一個佈局屬性的陣列,來儲存所有Cell的佈局重新整理,避免不必要的計算。
         ——> 放在哪? 計算最好呢? 
         ———>  首選放在prepareLayout方法中,因為它會呼叫一次載入,而且當重新整理的時候也會呼叫,滿足需求,我們先將之前的全部移除,然後重新返回最新的佈局屬性陣列; 但是,最好不要放在layoutAttributesForElementsInRect:方法(返回所有元素的佈局屬性陣列中),因為改方法在滾動collectionView的時候,會頻繁的呼叫,比較銷燬效能。
  • 3、在layoutAttributesForElementsInRect:方法(返回所有元素的佈局屬性陣列 )

    • 返回之前儲存的所有Cell的佈局屬性陣列
  • 4、我們可以在layoutAttributesForItemAtIndexPath: 方法: 來調整 Cell的佈局屬性 , 指定Cell的 frame 

    1.在該方法中拿到當前cell的預設佈局屬性attrs,進行下面的調整,就可以實現瀑布流了
    2.遍歷columnMaxYs陣列,需要找出最短一列的 列號 與 最大Y值
    3. 確定當前Cell的 存放的x.y位置 以及widht與height
           —> width:根據螢幕寬度與列數以及每列的寬度求出.  height:伺服器返回的
           —> x:需要根據當前最短一列的列號與Cell
    的寬度與間距可以求出來;y : 可以根據當前最短一列的最大Y值 + 行間距可以求出 4.重新設定修改當前Cell的佈局屬性attrs 的 frame即可。—> 之前已經拿到當前Cell的 預設佈局屬性,以及上一步已經獲取到當前Cell需要存放的x.y位置後, 5.將修改Cell佈局屬性之後的,當前列當前Cell的Y值最大,所有我們要將該值記錄到陣列columnMaxYs中,以便下次對比

5、注意:我們需要設定collectionView的contentSize,它才會滾動,那麼我們如何設定呢?

  ——> 在定義佈局類中,系統提供了一個collectionViewContentSize的get物件方法(決定collectionView的contentSize)
                  —> contentSize的高度為:計算出最長那一列的最大Y值,也就是columnMaxYs的最大值 + 行間距

三、瀑布流的基本實現


效果圖.png
  • 1.建立一個控制器JPCollectionViewController,繼承UICollectionViewController,使用我們自定義的流水佈局JPWaterflowLayout(實現見其後)
#import "JPCollectionViewController.h"
#import "JPWaterflowLayout.h" // 自定義流水佈局

@interface JPCollectionViewController ()

@end

@implementation JPCollectionViewController

static NSString * const reuseIdentifier = @"cellID";

- (void)viewDidLoad {
    [super viewDidLoad];

    // 切換佈局
    self.collectionView.collectionViewLayout = [[JPWaterflowLayout alloc] init];
}

#pragma mark <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 30;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    cell.backgroundColor = [UIColor orangeColor];
    return cell;
}
@end
  • 2.自定義流水佈局JPWaterflowLayout,繼承UICollectionViewLayout
#import "JPWaterflowLayout.h"

#define JPCollectionW self.collectionView.frame.size.width

/** 每一行之間的間距 */
static const CGFloat JPDefaultRowMargin = 10;
/** 每一列之間的間距 */
static const CGFloat JPDefaultColumnMargin = 10;
/** 每一列之間的間距 top, left, bottom, right */
static const UIEdgeInsets JPDefaultInsets = {10, 10, 10, 10};
/** 預設的列數 */
static const int JPDefaultColumsCount = 3;

@interface JPWaterflowLayout()
/** 每一列的最大Y值 */
@property (nonatomic, strong) NSMutableArray *columnMaxYs;
/** 存放所有cell的佈局屬性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
@end

@implementation JPWaterflowLayout

#pragma mark - 懶載入
- (NSMutableArray *)columnMaxYs
{
    if (!_columnMaxYs) {
        _columnMaxYs = [[NSMutableArray alloc] init];
    }
    return _columnMaxYs;
}

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [[NSMutableArray alloc] init];
    }
    return _attrsArray;
}

#pragma mark - 實現內部的方法
/**
 * 決定了collectionView的contentSize
 */
- (CGSize)collectionViewContentSize
{
    // 找出最長那一列的最大Y值
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // 取出第i列的最大Y值
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];

        // 找出陣列中的最大值
        if (destMaxY < columnMaxY) {
            destMaxY = columnMaxY;
        }
    }
    return CGSizeMake(0, destMaxY + JPDefaultInsets.bottom);
}

- (void)prepareLayout
{
    [super prepareLayout];

    // 重置每一列的最大Y值
    [self.columnMaxYs removeAllObjects];
    for (NSUInteger i = 0; i<JPDefaultColumsCount; i++) {
        [self.columnMaxYs addObject:@(JPDefaultInsets.top)];
    }

    // 計算所有cell的佈局屬性
    [self.attrsArray removeAllObjects];
    NSUInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSUInteger i = 0; i < count; ++i) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

/**
 * 說明所有元素(比如cell、補充控制元件、裝飾控制元件)的佈局屬性
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

/**
 * 說明cell的佈局屬性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    /** 計算indexPath位置cell的佈局屬性 */

    // 水平方向上的總間距
    CGFloat xMargin = JPDefaultInsets.left + JPDefaultInsets.right + (JPDefaultColumsCount - 1) * JPDefaultColumnMargin;
    // cell的寬度
    CGFloat w = (JPCollectionW - xMargin) / JPDefaultColumsCount;
    // cell的高度,測試資料,隨機數
    CGFloat h = 50 + arc4random_uniform(150);

    // 找出最短那一列的 列號 和 最大Y值
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    NSUInteger destColumn = 0;
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // 取出第i列的最大Y值
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];

        // 找出陣列中的最小值
        if (destMaxY > columnMaxY) {
            destMaxY = columnMaxY;
            destColumn = i;
        }
    }

    // cell的x值
    CGFloat x = JPDefaultInsets.left + destColumn * (w + JPDefaultColumnMargin);
    // cell的y值
    CGFloat y = destMaxY + JPDefaultRowMargin;
    // cell的frame
    attrs.frame = CGRectMake(x, y, w, h);

    // 更新陣列中的最大Y值
    self.columnMaxYs[destColumn] = @(CGRectGetMaxY(attrs.frame));

    return attrs;
}
@end