iOS使用CollectionView实现瀑布流

瀑布流的原理

将屏幕等分成三列 然后将图片加载到每一列中,在加入到列之前,首先判断那一列的高度最低,然后把图片加到高度最低的那列中。

瀑布流设计思路分析

使用UICollectionView,采用自定义布局的方式,设置cell的排列规则 完成瀑布流。

  1. 自定义布局中,指定滚动方向,默认列数,行间距,列间距,以及指定cell的大小itemSize
  2. 创建一个数组columnMaxYs(记录当前每一列的最大Y值)
  3. 通过判断记录的最大Y值是否为最小的一列 计算item的X坐标 和Y坐标 并记录添加上的item那一列的最大Y坐标 设置item的frame属性
  4. 当我们的item进入复用池的时候,我们的界面展示的我们眼睛看到的以及我们想的,是和事实不相符的。 当我们向上滑动的时候你感觉 Y值在无线变大,但其实我们的Y值在最上方还是0。这里就需要用到prepareLayout方法。这个方法会在item出现在屏幕上之前反复执行。
  5. 在第一次加载的时候我们会计算所有的item的布局属性,但是当我们上下滑动的时候,还是需要重新计算这些布局属性,所以我们需要提供一个布局属性数组存放Cell的布局属性,避免必要的计算。
  6. 我们需要知道如何返回我们计算出来的item的布局属性,并且在那里计算合适,不会出现数据丢失的现行, layoutAttributesForElementsInRect:(返回所有元素的布局属性数组)。
  7. 这里需要设置Collection的滚动属性 ,就需要设置她的ContentSize 。没关系系统已经给出了这样的方法collectionViewContentSize

瀑布流的基本实现代码

#import <UIKit/UIKit.h>

@interface ZQCollectionViewController : UICollectionViewController

@end
#import "ZQCollectionViewController.h"
#import "ZQCollectionViewCell.h"
#import "ZQCollectionViewLayout.h"
@interface ZQCollectionViewController ()
{
    NSMutableArray *arr;
}
@end

@implementation ZQCollectionViewController

static NSString * const reuseIdentifier = @"Cell";

- (void)viewDidLoad {
    [super viewDidLoad];

    arr=[NSMutableArray array];

    for (int i=0; i<49; i++) {

        NSString *string=[NSString stringWithFormat:@"%d.jpg",i%5+1];

        [arr addObject:string];
    }

    ZQCollectionViewLayout *layout=[[ZQCollectionViewLayout alloc]init];

    layout.imageList=arr;

    self.collectionView.collectionViewLayout=layout;

    [self.collectionView registerNib:[UINib nibWithNibName:@"ZQCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:reuseIdentifier];

}



- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}


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

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    ZQCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

    cell.imgV.image =[UIImage imageNamed:arr[indexPath.row]];

    cell.backgroundColor = [UIColor purpleColor];

    return cell;
}



@end
#import <UIKit/UIKit.h>

@interface ZQCollectionViewCell : UICollectionViewCell

//注意这里是使用XIB绘制的
@property (weak, nonatomic) IBOutlet UIImageView *imgV;


@end
#import "ZQCollectionViewCell.h"

@implementation ZQCollectionViewCell

@end
#import <UIKit/UIKit.h>

@interface ZQCollectionViewLayout : UICollectionViewLayout
@property(nonatomic,copy)NSArray *imageList;
@end
#import "ZQCollectionViewLayout.h"

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

//static 只在当前作用域使用 const 不可修改的
static const UIEdgeInsets NewDefaultInsets={10,10,10,10};

//定义行列之间的间距
static const CGFloat NewDefaultColumn=10;

//定义默认的列数
static int NewDeraultNumber=3;

@interface ZQCollectionViewLayout ()

//创建数组存放 Y值最大值 存放cell的布局属性
@property(nonatomic,strong)NSMutableArray *columnArr;
@property(nonatomic,strong)NSMutableArray *cellArr;

@end

@implementation ZQCollectionViewLayout

- (NSMutableArray *)columnArr{
    if (!_columnArr) {
        _columnArr=[NSMutableArray array];
    }
    return _columnArr;
}

- (NSMutableArray *)cellArr{
    if (!_cellArr) {
        _cellArr =[NSMutableArray array];
    }

    return _cellArr;
}


- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{

    UICollectionViewLayoutAttributes *attr=[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    //布局属性刷新更改

    //获取总的横向间距

    CGFloat xMARGIN=NewDefaultInsets.left+NewDefaultInsets.right+(NewDeraultNumber-1)*NewDefaultColumn;

    CGFloat width=(NewDefaultCollectionViewWidth-xMARGIN)/NewDeraultNumber;

#pragma mark -- 这里返回图片的高度

    UIImage *image=[UIImage imageNamed:self.imageList[indexPath.row]];

    CGFloat height=image.size.height *(width/image.size.width);



#pragma mark -- 这里我们需要获取x坐标的值 如何获取 因为我们要做的是将需要展示的数组按顺序向下排列 而顺序就将后进来的插入到 最短的那一列 所以要获取这个x坐标我们就需要找出这个最小的y坐标才能确定
    NSInteger sum=0;

#pragma mark -- 下面这个遍历为什么要使用也是上面的原因 取出最大的y值 并获取对应的列数 其实columnArr只有三个元素
    CGFloat sumMaxY=[self.columnArr[0] doubleValue];

    for (int i=0; i<self.columnArr.count; i++) {

        CGFloat anyMaxY=[self.columnArr[i]doubleValue];

        if (sumMaxY>anyMaxY) {

            sumMaxY=anyMaxY;

            sum=i;
        }
    }

    CGFloat x=NewDefaultInsets.left +sum*(width+NewDefaultColumn);

    CGFloat y=NewDefaultInsets.top+sumMaxY;

    attr.frame=CGRectMake(x, y, width, height);

    //更新数组,获取最大的Y 下一次比较时用到 记住每一次都会走下面这个方法 而这个方法 第一次的时候都是0 第二次的时候两个0 第三次的时候就不一样了
    self.columnArr[sum]=@(CGRectGetMaxY(attr.frame));

    return attr;
}

#pragma mark -- 这里下面的方法 是当我们向上或向下滑动item的时候 我们需要将我们的最大Y坐标重置 为什么呢 因为如果你继续使用最大Y坐标 它还是向下排列
- (void)prepareLayout{
    [super prepareLayout];

    //设置cell的最大Y值
    [self.columnArr removeAllObjects];

    for (int i=0; i<NewDeraultNumber; i++) {

        //这里使用下面的方法是给最大值一个初始值
        [self.columnArr addObject:@(NewDefaultInsets.top)];

    }

    //设置cell的布局属性 这里的self.layoutAttributesForItemAtIndexPath 是本类的一个属性 通过对应的indexPath我们可以拿到对应的item的布局属性 然后存储起来
    [self.cellArr removeAllObjects];

    NSInteger count=[self.collectionView numberOfItemsInSection:0];

    for (int i=0; i<count; i++) {

        NSIndexPath *indexPath=[NSIndexPath indexPathForItem:i inSection:0];

        UICollectionViewLayoutAttributes *attrs=[self layoutAttributesForItemAtIndexPath:indexPath];

        [self.cellArr addObject:attrs];
    }


}


#pragma mark -- 设置collectionView的范围 contentSize
- (CGSize)collectionViewContentSize{

    CGFloat sumMaxY=[self.columnArr[0] doubleValue];

    for (int i=0; i<self.columnArr.count; i++) {

        CGFloat anyMaxY=[self.columnArr[i]doubleValue];

        if (sumMaxY>anyMaxY) {

            sumMaxY=anyMaxY;
        }
    }

    //这里返回的横坐标是什么都可以
    return CGSizeMake(0, sumMaxY);
}
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{

    //在里我们将我们存储起来的item布局属性交付给cell
    return self.cellArr;
}

@end

你可能感兴趣的:(ios,瀑布流,布局)