• 瀑布流的实现


    1.什么是瀑布流?

    手机应用界面多数是矩阵排列的,比如掌阅的书架

    每一个方格子宽高相等,整整齐齐.据说此种布局容易造成视觉疲劳,于是希望将方格子的位置摆放不要这样整整齐齐,希望每一行的方格子看起来参差不齐,于是就有了瀑布流.如图:

    简单的说就是一种摆放控件的样式.

    2.如何实现?

    可以滚动,可以用Scrollview,tableView,collectionView实现,此处用collectionView,据说collectionView更强大,但是因为初学习,还没能充分体会到.

    collectioView的用法大致和tableview相同.不同在于

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

    1>此方法中,初次加载此方法在缓存池中找不到可重用单元格,需要注册单元格,不赘述

    2>collectionView的frame的设置全部交给一个类来处理了-----UICollectionViewLayout.

    这个类专门用来处理UICollectionView的大小,frame等属性.

    因为瀑布流实际上就是设置控件的frame,大小等属性,所以需要自定义布局,也就是需要自己写一个类继承自UICollectionViewLayout即可.苹果还封装了一个继承自UICollectionViewLayout的布局,叫做UICollectionViewFlowLayout,这个类新增了处理每一个item的大小,行间距,列间距等属性,但是此处可以不用继承这个类,直接继承UICollectionViewLayout即可,因为我们要用到的方法UICollectionViewLayout都具备.

    四个方法

    1>- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
    此方法用于设置rect范围中所有item的属性
    
    2>- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
    此方法用于设置indexpath对应的item的属性
    
    3>- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    返回YES,表示当界面的位置发生改变,自动更新布局,调用第一个方法.
    
    4>- (CGSize)collectionViewContentSize
    此方法用于设置可滚动范围.

    1)在方法2中计算每一个item的frame;

    具体实现如下:

       //返回一个属性
        //计算item的frame
        /**
         *  获取indexpath对应的cell的属性
         */
        UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        //自定义item的frame
        UIEdgeInsets inset = self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
        CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
        NSInteger column = 3;
        CGFloat padding = 10;
        //
        CGFloat attrW = (screenW - inset.left - inset.right - padding * (column - 1)) / column;
        ////    CGFloat attrH = arc4random_uniform(100);
    
         NSArray *modelArray = [GoodModel goods];
        GoodModel *goodModel = modelArray[indexPath.item];
    
        CGFloat attrH = attrW * goodModel.height / goodModel.width;
        //x
        //y
        CGFloat tempMaxY = MAXFLOAT;
        NSInteger lie = 0;
        for (NSInteger i = 0; i < 3; i ++) {
            CGFloat maxY = [self.maxY[i] doubleValue];
            if (maxY < tempMaxY) {
                tempMaxY = maxY;
                lie = i;
    
            }
        }
    
        CGFloat attrX = inset.left + (attrW + padding) * lie;
        CGFloat attrY = tempMaxY + padding;
    
        attr.frame = CGRectMake(attrX, attrY, attrW, attrH);
    
        //将最大Y值累加
        self.maxY[lie] = @(attrY + attrH);
    
        return attr;

    主要困难是计算X,Y,要计算X,实际上就是九宫格算法,X = 左边距 + 列数 * (item宽+间距);左边距,间距通常是给定的,因而关键在于求item对应的列数.当一行摆满以后,第二行从最短的那个开始摆放,这样不至于最后每一列的item总的宽高相差太大导致的不美观.因此需要获得最短的item(也就是获得最小的最大Y值)对应列数,首先要计算出最小的最大Y值,因而需要遍历每一行的item的最小的最大Y值,因而需要有一个数组存储每一个item的最大Y值,

    通过懒加载实现:

    - (NSMutableArray *)maxY
    {
        if (_maxY == nil) {
            _maxY = [NSMutableArray array];
        }
        return _maxY;
    }

    初始化为三个元素,值为0.问题是为什么用懒加载?不用行不行?初始化又在哪里初始化?为什么要在此处初始化?

    2)当计算好每一个item的属性后,在方法1中返回可见范围属性的集合,实际上就是获取方法2中设置好的每一个item的属性,并添加到数组中,

    方法1配合方法3实现滚动自动更新布局,我学习的这种做法很笨,因为在第二个方法中,返回的并不是rect范围内的所有item的属性的结合,而是plist文件中全部item(而不仅仅是界面上的),代码如下:

        //返回属性的数组
    
        //    [self.maxY removeAllObjects];
        //初始化最大Y值的数组
        for (NSInteger i = 0; i < 3; ++ i) {
            self.maxY[i] = @0;
        }
        //设置属性
        //获取item个数
        NSInteger count = [self.collectionView numberOfItemsInSection:0];
        //创建属性数组
        NSMutableArray *attrs = [NSMutableArray array];
        for (NSInteger i = 0; i < count; ++ i) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
            UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath];
            [attrs addObject:attr];
        }
        //返回属性的集合
        return attrs;

    3)最后,需要重写方法四,设置可滚动范围即可.

    3>这些方法的执行顺序:

    collectionViewContentSize(第1次)----layoutAttributesForElementsInRect(1)----layoutAttributesForItemAtIndexPath(1-100共100次)----collectionViewContentSize(第2次)----layoutAttributesForElementsInRect(2)----layoutAttributesForItemAtIndexPath(101-200)----

    collectionViewContentSize(3)

    轻轻滚动界面,

    shouldInvalidateLayoutForBoundsChange(1)----

    collectionViewContentSize(4)----layoutAttributesForElementsInRect(3)----layoutAttributesForItemAtIndexPath(201-300)

    collectionViewContentSize(5)----layoutAttributesForElementsInRect(4)----layoutAttributesForItemAtIndexPath(301-400)

    collectionViewContentSize(6)----

    shouldInvalidateLayoutForBoundsChange(2)----

    collectionViewContentSize(7)----layoutAttributesForElementsInRect(5)----layoutAttributesForItemAtIndexPath(401-500)

    collectionViewContentSize(8)----layoutAttributesForElementsInRect(6)----layoutAttributesForItemAtIndexPath(501-600)

    collectionViewContentSize(9)

    shouldInvalidateLayoutForBoundsChange(3)----

    collectionViewContentSize(10)----layoutAttributesForElementsInRect(7)----layoutAttributesForItemAtIndexPath(601-700)

    collectionViewContentSize(11)----layoutAttributesForElementsInRect(8)----layoutAttributesForItemAtIndexPath(701-800)

    collectionViewContentSize(12)

    4>因而可以知道

    初始化的时候方法调用顺序是:

    collectionViewContentSize(第1次)---- layoutAttributesForElementsInRect(1)---- layoutAttributesForItemAtIndexPath(1-100共100次)---- collectionViewContentSize(第2次)---- layoutAttributesForElementsInRect(2)----layoutAttributesForItemAtIndexPath(101-200)----

    collectionViewContentSize(3)

    滚动界面是调用顺序是:

    shouldInvalidateLayoutForBoundsChange----初始化时候的顺序.

    然而为什么以这样的顺序调用呢?

    由于在方法一中直接返回全部item属性,故而每次调用方法一就会调用很多次(item的个数)方法二,故而此种实现方法效率极低.

  • 相关阅读:
    86. 分隔链表
    85. 最大矩形
    84. 柱状图中最大的矩形
    82. 删除排序链表中的重复元素 II
    80. 删除排序数组中的重复项 II
    77. 组合
    java-xml
    java-反射
    springboot解决跨域问题(CorsConfig )
    解决oracle锁表
  • 原文地址:https://www.cnblogs.com/yufang/p/yufang.html
Copyright © 2020-2023  润新知