UIColletionView瀑布流布局实现思路以及封装的实现

瀑布流实现思路

  • 第一种就是用ScrollView来进行实现,由于它不具备复用的功能,因此我们需要自己写一套类似复用的模块来进行优化
  • 第二种就是利用apple做好的复用模块,自定义UIColletionLayout来实现瀑布流,想想也是第二种实现起来更快更优,OK,封装一个小小的框架来试试


    默认两列


    三列多倍间距

其他案例


上面的动画切换布局也是自定义UICollectionLayout来进行布局的,简单的静态图片布局展示其实就重写几个方法就可以了


1.prepareLayout 每次重新刷新collectionView的时候会调用一次,做一些初始化的工作
2.layoutAttributesForElementsInRect 返回已经制定好之后的每个cell对应的attribute属性对象进行布局
3.layoutAttributesForItemAtIndexPath 该方法会一直调用,每次cell出来就会根据对应的indexpath来进行方法调用,因此关键布局代码就可以放置在这里进行重新计算
4.collectionViewContentSize 计算整体的大小,实现滚动

上面插入样式实现的传送门

瀑布流实现分析

1.基本变量的声明

// 每一列的间距
static const CGFloat MKJDefaultColumnMargin = 10;
// 每一行间距
static const CGFloat MKJDefaultRowMargin = 10;
// 整体的上间距,左间距,下间距,右间距
static const UIEdgeInsets MKJDefaultEdgeInsets = {10,10,10,10};
// 默认是多少列
static const NSUInteger MKJDefaultColumnCounts = 2;

@interface MKJWaterFallLayout ()

@property (nonatomic,strong) NSMutableArray *attributeArr; // cell属性的数组
@property (nonatomic,strong) NSMutableArray *columnHeightArr; // 每列的高度数组

@end

2.初始化

// 每次刷新会调用一次
- (void)prepareLayout
{
    [super prepareLayout];

    // 每次重新刷新的时候清除之前的所有高度值,默认就是UIEdg给定的top
    [self.columnHeightArr removeAllObjects];
    for (NSInteger i = 0; i < [self columnCount]; i++) {
        [self.columnHeightArr addObject:@([self insetMargin].top)];
    }
    // 每次刷新把对应的att属性清空
    [self.attributeArr removeAllObjects];
    // 初始化一次每个cell对应的attribute属性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        NSIndexPath *indexpath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexpath];
        [self.attributeArr addObject:attribute];
    }
}

3.关键计算代码

// 返回attribute属性数组决定最后的排布
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attributeArr;
}

// 返回对应的indexpath下每个cell的属性 cell的出现会一直刷新该方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 初始化布局属性---> 对应的indexpath
    UICollectionViewLayoutAttributes *att = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    CGFloat collectionW = self.collectionView.frame.size.width;

    // 宽度是根据列数和间距固定算出来的
    CGFloat width = (collectionW - [self insetMargin].left - [self insetMargin].right - ([self columnCount] - 1) * [self columnMargin]) / [self columnCount];

    // 高度是根据代理的数据源返回比例计算的
    CGFloat height = [self.delegate MKJWaterFallLayout:self heightForItemAtIndexPath:indexPath] * width;
    // X 和 Y值都是在找出最小column之后才能确定,核心就是根据列数,找出最小高度的那一列
    // 先取出第一个默认是最小的
    CGFloat minColumnHeight = [self.columnHeightArr[0] doubleValue];
    // 默认最小的是第0列
    NSUInteger finalCol = 0;
    for (NSInteger i = 1 ; i < [self columnCount]; i++) {
        CGFloat currentColHeight = [self.columnHeightArr[i] doubleValue];
        if (minColumnHeight > currentColHeight) {
            minColumnHeight = currentColHeight;
            finalCol = i;
        }
    }

    // x,y值是根据最小高度列算出来的
    CGFloat x = [self insetMargin].left + (width + [self columnMargin]) * finalCol;
    CGFloat y = minColumnHeight;
    // 当你是一个行排布的时候 默认是top值,不需要加间距
    NSInteger count = indexPath.item;
    if ((count / ([self columnCount])) >= 1) {
        y += [self rowMargin];
    }
    att.frame = CGRectMake(x, y, width, height);
    self.columnHeightArr[finalCol] = @(CGRectGetMaxY(att.frame));
    return att;

}



这里的计算简概括为就是对每个cell进行frame的计算
1.宽度的计算是根据列间距和整体左右间距以及行数进行限制,这些参数可以是固定值,也可以是代理传进去的
2.高度的计算必定是根据外部代理进行计算的
3.X值的计算是根据这个框架内部的每一列的高度数组进行之前的缓存高度,进行最小值计算,然后拿出最小值对应的列数,根据上面算出来的高度进行X值的计算
4.Y值的计算和X值一样,根据给定的数组,比出最小高度列的列数,根据数组的高度,计算出对应的Y值,最终再进行数组对应列数的高度更新

4.获取实际能容大小,让其可以滚动

// 计算出滚动区域的大小
- (CGSize)collectionViewContentSize
{
    CGFloat maxColumHeight = [self.columnHeightArr[0] doubleValue];
    for (NSInteger i = 1; i < [self columnCount]; i++) {
        CGFloat currentColHeight = [self.columnHeightArr[i] doubleValue];
        if (maxColumHeight < currentColHeight) {
            maxColumHeight = currentColHeight;
        }
    }
    return CGSizeMake(0, maxColumHeight + [self insetMargin].bottom);

}

5.这个小框架已经封装好了,简单介绍下用法

// 声明如下
- (UICollectionView *)colletionView
{
    if (_colletionView == nil) {
        MKJWaterFallLayout *layout = [[MKJWaterFallLayout alloc] init];
        layout.delegate = self;
        UICollectionView *collectionV = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
        collectionV.backgroundColor = [UIColor redColor];
        [collectionV registerNib:[UINib nibWithNibName:productID bundle:nil] forCellWithReuseIdentifier:productID];
        collectionV.delegate = self;
        collectionV.dataSource = self;
        _colletionView = collectionV;

    }
    return _colletionView;
}

// 内部行数,间距的控制
#pragma mark - waterfallLayoutDelegate
- (CGFloat)MKJWaterFallLayout:(MKJWaterFallLayout *)layout heightForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 返回宽度和高度比例
    MKJProductModel *product = self.dataSource[indexPath.item];
    return product.h / product.w;
}

// 控制列间距
- (CGFloat)columnMarginForWaterFallLayout:(MKJWaterFallLayout *)collectionViewLayout
{
    return 10;
}
// 控制行间距
- (CGFloat)rowMarginForWaterFallLayout:(MKJWaterFallLayout *)collectionViewLayout
{
    return 30;
}
// 控制列数
- (NSUInteger)columnCountForWaterFallLayout:(MKJWaterFallLayout *)collectionViewLayout
{
//    if (self.dataSource.count > 50) {
//        return 3;
//    }
    return 3;
}
// 控制整体上左下右间距
- (UIEdgeInsets)insetForWaterFallLayout:(MKJWaterFallLayout *)collectionViewLayout
{
    return UIEdgeInsetsMake(10, 10, 10, 10);
}



一个简单的Demo以及一个框架就这样基本设计完了,需要的朋友可以直接下载拖到自己的工程里面试试,有问题请留言
Demo地址:
Demo和框架传送门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值