• 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

    此文仅为鄙人学习笔记之用,朋友你来了,如有不明白或者建议又或者想给我指点一二,请私信我。liuw_flexi@163.com/QQ群:582039935. 我的gitHub: (学习代码都在gitHub) https://github.com/nwgdegitHub/
  • 相关阅读:
    支付宝17年新春红包技术体系剖析
    从“扫月亮”到“扫福字”,扒一扒背后的支付宝AR框架体系
    【合集】支付宝春节红包背后的那些事——集五福,咻红包
    蚂蚁移动开发平台 mPaaS 3.0 智能化 + 生态化
    小程序 Serverless: 解放生产力,驱动研发效能提升
    深度解析:mPaaS 3.0全新组件
    vue--学习一
    .Net Mvc PagedList
    Integrated Security=SSPI
    IIS web site windows验证
  • 原文地址:https://www.cnblogs.com/liuw-flexi/p/7495131.html
Copyright © 2020-2023  润新知