瀑布流的簡單實現(xiàn)

很簡答的瀑布流,用collectionview實現(xiàn)cell高度不一致的情況下的排列。
主要就是自定義layout。
先看效果:


AAA-BBB.gif

自定義layout必須實現(xiàn)下面5個方法:

//完成布局前的初始工作
-(void)prepareLayout;

//collectionView的內(nèi)容尺寸
-(CGSize)collectionViewContentSize;

//為每個item設(shè)置屬性(被下面的那個方法調(diào)用)
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

//獲取制定范圍的所有item的屬性(調(diào)用的是上面的那個方法,存到數(shù)組)
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;

//在collectionView的bounds發(fā)生改變的時候刷新布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;

一、原理

layout文件負責collectionview的布局。所以在layout中計算完每個cell的坐標信息,就實現(xiàn)了瀑布流。所以瀑布流的重點是計算每個cell的位置信息。
cell的順序:從左上角是第0個cell,接著要放置第1個,這個怎么放?為了把屏幕鋪滿,只能放到最短的那列里。接著放第2個cell,也只放到最短的那一列里。以此類推,直到數(shù)據(jù)源里的最后一個。保證每次新增的cell放在最短的那一列。

經(jīng)過上面簡單的分析,cell的順序很重要。每次存放cell,都需要判斷最短是哪一列,然后再放。如圖:


排列順序.png

直接上代碼:
layout的h文件

#import <UIKit/UIKit.h>
//繼承自UICollectionViewFlowLayout
@interface PhotoLayout : UICollectionViewFlowLayout
//需要顯示幾列
@property (nonatomic, assign) NSInteger lines;
//左右cell的間距
@property (nonatomic, assign) CGFloat leftRSpace;
//上下cell的間距
@property (nonatomic, assign) CGFloat topBSpace;
//存放高度的數(shù)組,可以根據(jù)實際需求處理
@property (nonatomic, strong) NSMutableArray *heightArr; 
@end

m文件:為了代碼看著連貫,直接全部復(fù)制了。

#import "PhotoLayout.h"
#define SCREENW [UIScreen mainScreen].bounds.size.width

@interface PhotoLayout ()

@property (nonatomic, strong) NSMutableArray *attrsArray; //cell的屬性數(shù)組,存放cell位置信息

@property (nonatomic, strong) NSMutableArray *columnHeights;//高度數(shù)組,存放每列高度。有幾列這個數(shù)組就包含幾個對象
@property(nonatomic,assign)CGFloat cellWidth; //cell的寬度

@end


@implementation PhotoLayout

//布局的準備
- (void)prepareLayout {
    
    [super prepareLayout];
    
//    清空cell位置信息數(shù)據(jù)和高度數(shù)組
    [self.attrsArray removeAllObjects];
    [self.columnHeights removeAllObjects];
    
//    計算cell的寬度
    self.cellWidth = (SCREENW - self.sectionInset.left - self.sectionInset.right - (self.lines-1)*self.leftRSpace ) * 0.5;
    
//    存放最原始高度,這個時候還沒有cell的高度,其實就是section的上邊距
    for (int i = 0; i < self.lines; i ++) {
        [self.columnHeights addObject:@(self.sectionInset.top)];
    }

//    這里沒什么可注釋的,一看就明白。循環(huán)計算各個cell屬性。
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

//cell屬性數(shù)組的懶加載
- (NSMutableArray *)attrsArray{
    if (_attrsArray == nil) {
        _attrsArray = [[NSMutableArray alloc] init];
    }
    return _attrsArray;
}

//高度數(shù)組的懶加載。根據(jù)自己需求,可以不用數(shù)組一次性計算所有數(shù)據(jù)的高度。也可以實時計算高度。
- (NSMutableArray *)columnHeights {
    if (_columnHeights == nil) {
        _columnHeights = [[NSMutableArray alloc] init];
    }
    return _columnHeights;
}

//計算每個cell的屬性信息
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
//    初始化屬性
    UICollectionViewLayoutAttributes *atts = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
//    根據(jù)高度數(shù)組,循環(huán)一次,找出最短的那一列。就可以確定這個cell放到哪一列了。
//    取出第0個元素的高度
    NSInteger columnIndex = 0;
    CGFloat minHeight = [self.columnHeights[0] floatValue];
    for (NSInteger i = 1; i < self.lines; i ++) {
//        根據(jù)第0個高度,和其他高度對比
        CGFloat cellHeight = [self.columnHeights[i] floatValue];
//        確定最短的那個高度是多少,并記錄是哪一列的高度
        if (minHeight > cellHeight) {
            minHeight = cellHeight;
            columnIndex = i;
        }
    }

//    根據(jù)section邊距,cell左右間隙和上面確定的cell所在的列,計算出cell的x坐標
    CGFloat cellX = self.sectionInset.left + columnIndex * (self.leftRSpace + self.cellWidth);
    
//    根據(jù)確定的最短列的高度和cell的上線間隙,確定cell的y坐標
    CGFloat cellY = minHeight + self.topBSpace;
    
//    根據(jù)高度數(shù)組,確定cell的高度
//    --PS根據(jù)自己需求,可以不用數(shù)組一次性計算所有數(shù)據(jù)的高度。也可以實時計算高度。
    CGFloat cellH = [self.heightArr[indexPath.row] floatValue];
//    最后確定cell的frame
    atts.frame = CGRectMake(cellX, cellY, self.cellWidth, cellH);

//    由于新增了一個cell,更新這一列的高度
    self.columnHeights[columnIndex] = @(minHeight + self.topBSpace + cellH);
    
    return atts;
}


//通過調(diào)用上面的方法,把所有cell的屬性方法一個數(shù)組里
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    
    return self.attrsArray;
}

//實時更新collectionview的contentsize,由于每次屏幕上新出現(xiàn)cell就重新布局方法,contentsize也會變化
- (CGSize)collectionViewContentSize{
    
//    還是先取出第0列的高度
    NSNumber *longestNum = self.columnHeights[0];
    CGFloat longest = [longestNum floatValue];

//    循環(huán)一下哈,得到最長的高度。為了顯示完整,所以contentsize是有最長的列的決定的。
    for (NSInteger i = 1; i < self.columnHeights.count; i++) {
        NSNumber* rolHeight = self.columnHeights[i];
        if(longest < rolHeight.floatValue){
            longest = rolHeight.floatValue;
        }
    }
    
//    最長的列的高度,最后再加上section的底部間距
    return CGSizeMake(self.collectionView.frame.size.width, longest + self.sectionInset.bottom);
}

//在collectionView的bounds發(fā)生改變的時候刷新布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return !CGRectEqualToRect(self.collectionView.bounds, newBounds);
}

@end

二、使用

以上layout文件全局說完了。在控制器的調(diào)用就簡單多了。

//    layout對象最好寫成全局變量,實際開發(fā)中,數(shù)據(jù)是在網(wǎng)絡(luò)請求后得到,所以高度的確定也在網(wǎng)絡(luò)請求后,
    self.layout = [[PhotoLayout alloc] init];
//    為了代碼的重復(fù)使用方便,這些數(shù)據(jù)由控制器決定。
    self.layout.sectionInset = UIEdgeInsetsMake(0, 25, 0, 25);
    self.layout.lines = 2;
    self.layout.leftRSpace = 15;
    self.layout.topBSpace = 15;
    
    self.layout.heightArr = [[NSMutableArray alloc] init];
    
    self.collView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.layout];
    self.collView.alwaysBounceVertical = YES;
    self.collView.backgroundColor = [UIColor whiteColor];
    self.collView.delegate = self;
    self.collView.dataSource = self;
    [self.view addSubview:self.collView];
//網(wǎng)絡(luò)請求
- (void)loadNetData {
    for (int i = 0; i < 50; i++) {
        //模擬數(shù)據(jù),得到不一樣的高度而已。
        if (i % 3 == 0) {
            [self.layout.heightArr addObject:@(150)];
        }else{
            [self.layout.heightArr addObject:@(100)];
        }
    }
    [self.collView reloadData];
}

OVER!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容