瀑布流框架的搭建

瀑布流你們都應該熟悉了,如今大部分電商應用中或多或少的都用到瀑布流,它能夠吸引用戶的眼球,使用戶不易產生視覺疲勞,蘋果在iOS6中增添了UICollectionView控件,這個控件能夠說是UITableView的升級版,經過這個控件咱們就能很簡單的作出瀑布流,後面經過本身的封裝可讓其變成一個小框架,更簡單的應用到咱們以後的開發中數組

最近開通了簡書歡迎你們關注,我會不按期的分享個人iOS開發經驗  點擊關注-->Melody_Zhy緩存

若是想作瀑布流,那麼就要自定義CollectionViewFlowLayout,由於這個類中有一個返回collectionView中全部子控件的佈局屬性(佈局屬性中有控件的frame和索引)的方法框架

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *arr = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes *attri in arr) {
        attri.frame = CGRectMake(0, 0, 100, 300);
    }
    NSLog(@"%@", arr);
    return arr;
}

這個方法會將collectionView中的全部子控件的佈局屬性計算一次,計算以後就會被緩存起來,當已經計算過的cell,再次出現時也不會在重複去計算它的尺寸。佈局

注意:若是要進行刷新數據那麼要記得將以前的佈局屬性進行清空,否則會出現佈局錯誤
// 把用來裝全部佈局屬性的數據作清空處理
[self.attrArrM removeAllObjects];

如今定義一個可變數組屬性來存儲一會本身計算的佈局屬性中的frame,讓上面的方法返回本身定義的佈局屬性ui

// 用來保存全部佈局屬性的可變數組
@property (nonatomic, strong) NSMutableArray *attrArrM;

那麼咱們在哪一個方法中計算本身定義的佈局屬性呢?atom

有這麼個方法 spa

- (void)prepareLayout {
// 要調用父類的prepareLayout
    [super prepareLayout];
}

當collectionView中的全部子控件即將顯示的時候就會來調用此方法作佈局前的準備工做,準備itemSize...等等屬性 同時當佈局的屬性發生變化時也會來調用此方法 當刷新數據以後也會來調用此方法從新作佈局前的準備工做

在這個方法中能夠經過collectionViewFlowLayout的collectionView的numberOfItemInSection這個方法得到一組中的全部cell代理

// 得到一組中的全部cell
NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];

經過for循環(循環次數爲一組中有多少個cell)建立佈局屬性,在循環中須要計算每個cell的frame 因此後臺要給真實的圖片尺寸(若是不給,本身計算的尺寸會形成圖片閃)code

同時顯示時通常都會等比例縮放。orm

cell寬度的計算

cell的寬 =  (內容的寬 - (列數 - 1) * 最小間距) / 列數
內容的寬 = collectionView的寬 - 組的左邊間距 - 右邊間距
CGFloat contentWidth = self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right;
CGFloat cellW = (contentWidth - (self.columnCount - 1) * self.minimumInteritemSpacing) / self.columnCount;

cell高度的獲取

當要得到cell高的時候須要經過控制器來得到模型圖片的高度(高度要和itemW有必定的比例要不圖片會過大 height / width * itemW;),所以須要讓控制器成爲咱們的代理,在自定義CollectionViewFlowLayout.h文件中定義協議以下:

#import <UIKit/UIKit.h>
@class ZHYCollectionViewFlowLayout;
@protocol ZHYCollectionViewFlowLayoutDelegate <NSObject>
@required
- (CGFloat)waterFallFlowLayoutWithItemHeight:(ZHYCollectionViewFlowLayout *)flowLayout itemW:(CGFloat)itemW CellIndexPath:(NSIndexPath *)indexPath;
@end
而且設置代理屬性以下:
@property (weak, nonatomic) id<ZHYCollectionViewFlowLayoutDelegate> delegate

在計算cell高的時候調用代理方法得到cell的高度

CGFloat cellH = [self.delegate waterFallFlowLayoutWithItemHeight:self itemW:cellW CellIndexPath:indexPath];
 爲何要在代理方法中加入indexPath,由於要經過indexPath來獲取模型

cellX的計算

在計算cellX的時候,若是經過 NSInteger col = i % self.columnCount;獲取列號

要注意一個問題:

這樣每次都是按順序排列圖片的,那麼若是圖片的尺寸良莠不齊有的特別短有的又特別長,不巧的是長的都在一列短的又都在一列這樣會形成美觀性會不好,那麼怎麼解決這個問題呢,咱們能不能讓每一行添加完畢後,下一行在添加的時候將第一個添加在上一行高度最短的那面圖片下面呢?答案固然是能夠的-_-!

這時候咱們就要定義一個可變字典屬性,來存儲每一列的高度,用列號來當字典的key

// 用來記錄每一列的最的高度
@property (nonatomic, strong) NSMutableDictionary *colDict;
在prepareLayout方法中給每一列的高度的字典一個默認的高度
for (NSInteger i = 0; i<有幾列; i++) {
        NSString *str = [NSString stringWithFormat:@"%ld", i];
        self.colDict[str] = @(self.sectionInset.top);
    }
得到最矮的那一列列號的方法
- (NSString *)minCol {
    __block NSString *min = @"0";
    [self.colDict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if ([obj floatValue] < [self.colDict[min] floatValue]) {
            min = key;
        }
    }];
    return min;
}
所以列號爲
NSInteger col = [[self minCol] integerValue];
 cellX爲:
CGFloat cellX = self.sectionInset.left + (cellW + self.minimumInteritemSpacing) * col;

cellY的計算

計算cellY

// 用列號當字典的key
NSString *colStr = [NSString stringWithFormat:@"%ld", col];
CGFloat cellY = [self.colDict[colStr] floatValue];
// 累計每一列的高度
self.colDict[colStr] = @(cellY + cellH + self.minimumLineSpacing);

這樣咱們就計算完了每個cell的X,Y,W,H,咱們來設置佈局屬性的frame

// 設置屬性的frame
attr.frame = CGRectMake(cellX, cellY, cellW, cellH);
同時咱們也給尾部視圖添加一個佈局屬性,代碼以下
// 建立尾部視圖的佈局屬性
    // 建立footerview索引
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
    // 必須是額外的layoutAttributesForSupplementaryViewOfKind
    UICollectionViewLayoutAttributes *footerAttr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind: UICollectionElementKindSectionFooter withIndexPath:indexPath];
    footerAttr.frame = CGRectMake(0, [self.colDict[self.maxCol] floatValue] - self.minimumLineSpacing, self.collectionView.bounds.size.width, 50);
    [self.attrArrM addObject:footerAttr];
這裏要用到最高列,由於尾部視圖要放在cell最下面,得到最高列索引的方法爲:
// 用來取出最高那一列的列號
- (NSString *)maxCol {
    __block NSString *maxCol = @"0";
    [self.colDict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if ([obj floatValue] > [self.colDict[maxCol] floatValue]) {
            maxCol = key;
        }
    }];
    return maxCol;
}
最後咱們經過上面說過的方法把佈局屬性返回
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return self.attrArrM;
}
自定義佈局屬性的時候還要注意返回真實的contentSize,代碼以下:
- (CGSize)collectionViewContentSize {
    return CGSizeMake(0, [self.colDict[self.maxCol] floatValue] + self.footerReferenceSize.height - self.minimumLineSpacing);
}

這時基本已經完成了,但若是我想要把這個瀑布流佈局作成一個簡單的框架就須要在簡單的實現些初始化方法

在CollectionViewFlowLayout.h還要定義一個一行有幾個cell的屬性,當控制器引用這個類以後能夠自行設置

@property (assign, nonatomic) NSInteger columnCount;

提供一些初始化方法,使其默認爲一行有3個cell, cell間距及行間距爲10,內邊距爲頂部20, footerReferenceSize, headerReferenceSize都爲50,50

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.columnCount = 3;
        self.minimumInteritemSpacing = 10;
        self.minimumLineSpacing = 10;
        self.footerReferenceSize = CGSizeMake(50, 50);
        self.headerReferenceSize = CGSizeMake(50, 50);
        self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0);
    }
    return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        self.columnCount = 3;
        self.minimumInteritemSpacing = 10;
        self.minimumLineSpacing = 10;
        self.footerReferenceSize = CGSizeMake(50, 50);
        self.headerReferenceSize = CGSizeMake(50, 50);
        self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0);
    }
    return self;
}

這樣咱們簡單的瀑布流框架就搭建成功了~

相關文章
相關標籤/搜索