自定义layout实现瀑布流_UICollectionView

控制器中:
@interface ViewController () <UICollectionViewDelegateFlowLayout,UICollectionViewDataSource>

@property (nonatomic,strong) UICollectionView *collectionView;

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    WaterFlowLayOut *layout = [[WaterFlowLayOut alloc] init];
    _collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
    
    _collectionView.delegate = self;
    _collectionView.dataSource = self;
    _collectionView.backgroundColor = [UIColor lightGrayColor];
    
    [self.view addSubview:_collectionView];
    
    
    NSMutableArray *tmpArray = [NSMutableArray array];
    for (int i = 21; i >= 1; i--) {
        NSString *imageName = [NSString stringWithFormat:@"%d.jpg",i];

        NSString *path = [[NSBundle mainBundle] resourcePath];
        path = [path stringByAppendingString:[NSString stringWithFormat:@"/%@",imageName]];
        
        NSLog(@"%@",imageName);
        
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
        
         [tmpArray addObject:image];
    }
    
    [tmpArray addObjectsFromArray:tmpArray];
    [tmpArray addObjectsFromArray:tmpArray];
    self.images = tmpArray;
    
    
    
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.images.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    
    
    static NSString *identifier = @"cell";
    
    // 1. 注册
    [_collectionView registerClass:[WaterFallCell class] forCellWithReuseIdentifier:identifier];
    
    // 2. 复用池中寻找或者是直接创建
    WaterFallCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];

    // 给cell图片,之后,然后cell自己描绘
    cell.image = self.images[indexPath.item];
    
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    UIImage *image = self.images[indexPath.item];
    float height = [self imgHeight:image.size.height width:image.size.width];
    
    return CGSizeMake(100, height);
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    
    return UIEdgeInsetsMake(5, 5, 5, 5);
}

- (float)imgHeight:(float)height width:(float)width {
    
    // 根据宽100 通过比例或者当前图片的高度 每个图片高度不一样
    float newHeight = height/width*100;
    //     newHeight/100 = height/width;
    return newHeight;
}
cell中
#import "WaterFallCell.h"

@implementation WaterFallCell

- (void)setImage:(UIImage *)image {
    if (_image != image) {
        
        _image = image;
    }
    // 通过此方法调用drawRect方法
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    // 根据比例算出应该的高度
    float newHeight = _image.size.height/_image.size.width*100;
    [_image drawInRect:CGRectMake(0, 0, 100, newHeight)];
}

 自定义layout继承自UICollectionViewFlowLayout
#define Cols_Count 3
#import "WaterFlowLayOut.h"

@interface WaterFlowLayOut ()
@property (nonatomic,strong) NSMutableDictionary *attributesDic;

/**
 *  cell个数
 */
@property (nonatomic,assign) NSInteger cellCount;

/** 列高数组*/
@property (nonatomic,strong) NSMutableArray *colHeightArr;

/**
 *  间隙
 */
@property (nonatomic,assign) UIEdgeInsets edge;
@end

@implementation WaterFlowLayOut

/**
 *  1. 在这个方法中为每个cell做布局,为每个cell确定自己的位置,此处用三列展示图片,列数可以随意设置,取决于图片的宽度
 
 首先创建一个数组,存储三列的高度,找出最短列。
 
 将cell插入到最短列,而cell的高度和宽度 可以通过调用前面视图控制器实现的代理协议得到
 */

/**
 *  此方法中用了很多 self.delegate来调用代理方法 self.delegate即帮助来显示布局的控制器  collectionView的代理和布局的代理是同一个-》控制器
 */

- (void)prepareLayout {
    [super prepareLayout];
    
    _attributesDic = [NSMutableDictionary dictionary];
    
    // 让collectionView的代理 做  layout的代理
    self.delegate = (id<UICollectionViewDelegateFlowLayout>) self.collectionView.delegate;
    
    // 求出总共有多少个cell
    self.cellCount = [self.collectionView numberOfItemsInSection:0];
    // 如果0个cell直接就不layout了
    
    if (self.cellCount == 0) {
        return;
    }
    
    self.colHeightArr = [NSMutableArray array]; /// 列数,此处为三列,数组中存储每列的高度
    
    float top = 0;
    // 初始话高度都是0
    for (int i = 0; i < Cols_Count; i++) {
        [self.colHeightArr addObject:[NSNumber numberWithFloat:top]];
        
        
    }
    
    // 循环调用layoutAttributesForItemAtIndexPath方法为 每个cell计算布局 ,将indexPath传入 作为布局字典的key
    for (int i = 0; i < self.cellCount; i++) {
        
        // 这样写是完全错误的,collectionView的cell的位置是用indexPathForItem来定义的 ,这个ForRow是用来定义表视图的
        //     /   [self layoutForItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
        
        [self layoutForItemAtIndexPath: [NSIndexPath indexPathForItem:i inSection:0]];
    }
    
}

/**
 *  这里用到了一个布局字典,
 其实就是将每个cell的位置信息与indexPath相对应,将它们存入字典中,之所以用到这个字典,是为了后面视图的检索
 
 思路,将每个cell的frame动态算出,然后将其与自己特定的indexPath对应,放入字典,后面视图的检索(只返回屏幕可显示的cell不会一次全部返回所有cell)
 */
// 这个方法会调用多次,为每个cell布局
- (void)layoutForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    // 1. 间隙
    // 控制器调用方法(协议方法)来获取cell间的间隙
    UIEdgeInsets edgeInsets = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:indexPath.row];
    
    // 间隙
    self.edge  = edgeInsets;
    
    // 2. 大小
    // 每个cell的size
    CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
    
    NSInteger col =  0; // 列数
    // 找出高度最小列,然后将cell加入到高度最小列中
    float shortHeight = [self.colHeightArr[col] floatValue];
    
    //    NSLog(@"count = %ld",self.colHeightArr.count);
    // 遍历存放每列高度的数组
    for (int i = 1; i < self.colHeightArr.count; i++) {
        
        float colHeight = [self.colHeightArr[i] floatValue];
        if (colHeight < shortHeight) {
            
            shortHeight = colHeight;
            // 高度最小列的索引保存
            col = i;
        }
    }
    
    // 3.存放高度最小列高度
    float top = [self.colHeightArr[col] floatValue];
    
    //    NSLog(@"top =  %.1f",top);
    
    // 确定每个cell的frame
    
    // 起始位置x为 间距x + 前面的所有cell的宽和间隙 之和
    CGFloat cellX           = edgeInsets.left + col * (edgeInsets.left+itemSize.width);
    
    // 起始位置y为 高度最小列的高度 + 间隙
    CGFloat cellY           = top + edgeInsets.top;
    
    NSLog(@"celly:%.1f",cellY);
    
    // 宽度
    CGFloat cellWidth       = itemSize.width;
    
    // 高度
    CGFloat cellHeight      = itemSize.height;
    
    NSLog(@"celleheight = %.1f",cellHeight);
    
    //    NSLog(@"%f",cellHeight);
    
    CGRect cellFrame =  CGRectMake(cellX, cellY, cellWidth, cellHeight);
    
    //NSLog(@"%@",NSStringFromCGRect(cellFrame));
    //    NSLog(@"%f",cellFrame.size.height);
    
    // 4.cell加入后,更新列高
    [self.colHeightArr replaceObjectAtIndex:col withObject:[NSNumber numberWithFloat:top + edgeInsets.top + itemSize.height]];
    
    // 5.每一个cell的frame对应一个indexPath,存入字典
    //    [NSIndexPath indexPathForItem:i inSection:0]; 每一个cell都有一个indexPath
    [self.attributesDic setObject:indexPath forKey:NSStringFromCGRect(cellFrame)];
    
}


/**
 *  此方法会传入一个collectionView当前可见的rect,视图滑动时候调用
 
 // 需要返回每个cell'的布局信息,通过indexPath返回   (在rect范围内的)
 *
 
 需要返回每个cell的布局信息,如果忽略传入的rect一次性将所有的cell布局信息返回,,通过下面的两个方法,每次只将屏幕需要显示的cell返回,就不会造成图片过多时性能很差的效果
 
 //    UICollectionViewLayoutAttributes这个类包含了collectView内item的所有相关布局属性
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    //    Returns the layout attributes for all of the cells and views in the specified rectangle.
    // api解释;这个方法用来返回特定范围内的‘所有’ cell和视图的 layout属性
    /**
     *  1. 思路,怎么将特定范围内的所有的cell和视图的布局信息返回
     解决,每个cell都有特定的indexPath,通过indexPath得到每个cell的布局信息,然后加入数组返回即可!!
     // 但是所有的cell的indexPath们怎么取到?  必须得自己写方法来获取indexPathOfItemsInRect
     通过indexPathOfItemsInRect得到了所有的cell的indexPath们。这个时候就可以获取需要展示的所有的cell的布局信息
     同时这个过程中,需要layoutAttributesForItemAtIndexPath返回每个cell的布局信息。
     */
    
    
    NSMutableArray *attributes = [NSMutableArray array];
    
    
    // 调用indexPathOfItemInRect方法,将rect传入,计算当前rect应该出现的cell,返回值为cell的indexPath数组
    NSArray *indexPaths = [self indexPathOfItemsInRect:rect];
    
    for (NSIndexPath *indexPath in indexPaths) {
        //
        UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
        /**
         *  frame center bounds size alpha等
         */
        [attributes addObject:attribute];
    }
    
    return attributes;
}


// 为每个cell布局完毕之后,还要实现一个很重要的方法
- (NSArray *)indexPathOfItemsInRect:(CGRect)rect {
    
    /**
     *  遍历布局字典通过CGRectInterssectsRect方法确定每一个cell的rect和传入的rect是否有交集,如果为true,则cell应该显示,
     将布局字典中对应的indexPath存入数组中
     */
    
    NSMutableArray *indexPaths = [NSMutableArray array];
    
    for (NSString *rectString in self.attributesDic.allKeys) { // 此时self.attributesDic中以及保存了key是frame,值是indexPath的字典,而且是全部cell的,我们只需要遍历这个字典,如果这个字典中的cell的rect在当前显示的范围,此处为屏幕内,就取出indexPath加入数组,准备展示!!
        // {@"rectString":@indexPath}
        CGRect cellFrame = CGRectFromString(rectString);
        //        如果有交集 则将indexPath取出存入数组中
        if (CGRectIntersectsRect(rect, cellFrame)) {
            
            NSIndexPath *indexPath = self.attributesDic[rectString];
            [indexPaths addObject:indexPath];
        }
    }
    return indexPaths;
    
}


/**
 *  每一个cell的属性布局信息,
 *  返回单个cell属性布局信息
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    
//    [self.attributesDic setObject:indexPath forKey:NSStringFromCGRect(cellFrame)];
    
    
    UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    for (NSString *rectString in self.attributesDic.allKeys) {
        
        if (self.attributesDic[rectString] == indexPath) {

            attribute.frame = CGRectFromString(rectString);
            return attribute;
        }
        
    }
    return nil;
}




// collectionView的内容高度
- (CGSize)collectionViewContentSize {
    
    CGSize size = self.collectionView.frame.size;
    
    // 找出三列中最高的一列,作为collectionView的高度
    float maxHeight = [self.colHeightArr[0] floatValue];
    
    for (int i = 1; i < self.colHeightArr.count; i++) {
        float colHeight = [self.colHeightArr[i] floatValue];
        
        if (colHeight > maxHeight) {
            maxHeight = colHeight;
        }
    }
    size.height  = maxHeight;
    return size;
    
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值