MJ瀑布流学习笔记

1. 如果系统自带的布局的话,是这样:

    //系统自带的UICollectionViewFlowLayout 而不是UICollectionViewLayout
    UICollectionViewFlowLayout *waterLayout = [[UICollectionViewFlowLayout alloc]init];
    waterLayout.itemSize = CGSizeMake(100, 300);
    waterLayout.minimumLineSpacing = 5;
    waterLayout.minimumInteritemSpacing = 5;
    waterLayout.scrollDirection = UICollectionViewScrollDirectionVertical;

而自定义的话:WaterFlowLayout : UICollectionViewLayout

系统UICollectionViewFlowLayout也是继承自UICollectionViewLayout


2.  主要实现部分:
在- (void)prepareLayout;方法中计算好所有item的布局属性,主要是frame属性,而frame属性包括(x,y, w,h)

因为系统的这个方法

//滚动时是否重新计算
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}

所以- (void)prepareLayout;计算前都要做一次清空:

/**
 *  每次布局之前的准备 最先调用
 */
- (void)prepareLayout{

    NSLog(@"prepareLayout");//调一次
    // 1.清空最大的Y值 清空是为了防止滚动个计算出问题
    for (int i = 0; i<self.columnsCount; i++) {
        NSString *column = [NSString stringWithFormat:@"%d", i];
        //NSLog(@"self.sectionInset.top: %f",self.sectionInset.top);//10
        self.maxYDict[column] = @(self.sectionInset.top);//sectionInset有上下左右
    }
    
    // 2.计算所有cell的属性
    [self.attrsArray removeAllObjects];
    //获得有多少个item
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0 ; i< count; i++) {
        //这个获取属性方法下面有重写
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        [self.attrsArray addObject:attrs];
    }
}

上面代码中这部分,为每个item设置属性,然后扔到一个属性数组,所以重点在这里

UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];

重写他:

//重写 计算item属性方法 2 思路从需要的值往前推 这个方法最重要
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
//    NSLog(@"self.maxYDict %@",self.maxYDict);
//    {
//        0 = 30;
//        1 = 30;
//        2 = 30;
//    }

    //假设一个最小值
    __block NSString *minColumn = @"0";
    [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString * column, id  _Nonnull obj, BOOL * _Nonnull stop) {
        //找到每一行最短的那个列 0/1/2 后面往这个列加下一张图片的height
        if ([obj floatValue] < [self.maxYDict[minColumn] floatValue]) {
            minColumn = column;
        }
    }];
    
    //计算尺寸
    CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.columnsCount -1)*self.columnMargin)/self.columnsCount;
    //CGFloat height = 100 + arc4random_uniform(100);//先随机给个高度 这样做滚动时会出现烟花缭乱的
    CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];//让代理去帮忙计算
    
    //NSLog(@"height %f",height);
    //计算位置
    CGFloat x = self.sectionInset.left + (width + self.columnMargin) *[minColumn intValue];
    CGFloat y = [self.maxYDict[minColumn] floatValue] + self.rowMargin;
    self.maxYDict[minColumn] = @(y + height);
    
    // 创建属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attrs.frame = CGRectMake(x, y, width, height);
    return attrs;
}

系统有一个方法会返回所有属性,而返回值是一个数组,数组里装的就是所有item的布局属性,所以重写这个方法:

//重写返回所有item属性方法
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSLog(@"layoutAttributesForElementsInRect");
    //在prepare一次性算好 然后扔过来
    return self.attrsArray;
}

最后整块UICollectionView的content又一个大小需要给出来,重写下面这个方法即可,也就是最大的y+height :

//重写 返回collectionView的 Size方法 这个要计算完所有item才知道
//这个方法子类和父类都会调一次 所以会调2次 不奇怪
-(CGSize)collectionViewContentSize{
    //[super prepareLayout];
    
    __block NSString *maxCloumn = @"0";
    [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if ([obj floatValue] > [self.maxYDict[maxCloumn] floatValue]) {
            maxCloumn = key;
        }
    }];
    NSLog(@"collectionViewContentSize - %f",[self.maxYDict[maxCloumn] floatValue]);//一次全部item布局调2次
    return CGSizeMake(0, [self.maxYDict[maxCloumn] floatValue]);//0表示不支持水平滚动,最大y值可以遍历
    
}

另外:因为每个item的高度是要计算的,可以写个私有方法,也可以让ViewController来成为代理,让他帮忙计算。这里用代理实现

//CGFloat height = 100 + arc4random_uniform(100);//先随机给个高度 这样做滚动时会出现烟花缭乱的
    CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];//让代理去帮忙计算

利用代理的好处,MJ老师说了:

利用控制器成为自定义布局类的代理,返回想要的高度。利用代理的好处,就是在布局想拿到控制器一些东西的时候,防止布局系统需要的是一个模型时,如果你利用布局一个属性来传值,那么布局系统永远跟模型相关了。为了防止这种依赖关系,所以用代理来做,比较合适,框架类的东西不容易产生对外部代码的依赖。通用型较好。

 

Demo地址:https://github.com/nwgdegitHub/Waterfalls-flow

另外这边文章实现原理大致相同,值得借鉴。

https://www.jianshu.com/p/995c0f35e7fb

 

posted @ 2017-09-08 16:05  liuw_flexi  阅读(383)  评论(0编辑  收藏  举报