zoukankan      html  css  js  c++  java
  • 简单瀑布流的实现

    关于简单瀑布流的实现:


    瀑布流是基于UICollectionView来实现的,主要通过自定义的一个继承于UICollectionViewFlowLayout的类


    首先,实现基本的界面实现.

    然后,在自定义的一个继承于UICollectionViewFlowLayout的类QHLFlowLayout中.
    瀑布流的实现,主要是重写了4个方法来实现
    在继承于UICollectionViewFlowLayout的QHLFlowLayout.h文件中

    @protocol QHLFlowLayoutDelegate <NSObject>
    
    - (CGFloat)flowLayout:(QHLFlowLayout *)flowLayout heightForItems:(NSIndexPath *)indexPath itemWithWidth:(CGFloat)width;
    
    - (NSInteger)flowLayoutNumberOfItemColumnCount:(QHLFlowLayout *)flowLayout;
    
    - (CGFloat)flowLayoutMarginBetweenItems:(QHLFlowLayout *)flowLayout;
    
    - (UIEdgeInsets)flowLayoutSectionInsetOfItems:(QHLFlowLayout *)flowLayout;
    
    @end

    以上是自定义的flowLayout的代理方法.

    通过代理,来实现对每个item的高度,item的间距,每列的个数以及内间距的赋值(如果没有值传入的话,会使用默认值)

    在继承于UICollectionViewFlowLayout的QHLFlowLayout.m文件中

    定义一些宏定义

    #define kColumnCount [self numberOfItemColumn]
    #define kMargin [self marginBetweenItems]
    #define kSectionInset [self sectionInsetOfFlowLayout]
    #pragma mark - 返回每行列数
    - (NSInteger)numberOfItemColumn {
        NSInteger count = 0;
        
        if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutNumberOfItemColumnCount:)]) {
            
            count = [self.flowLayoutDelegate flowLayoutNumberOfItemColumnCount:self];
        } else {
            count = 3;  //外界不传高度进来的话  默认每行列数是100
        }
        
        return count;
    }
    #pragma mark - 返回间距
    - (CGFloat)marginBetweenItems {
        CGFloat margin = 0;
        if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutMarginBetweenItems:)]) {
            margin = [self.flowLayoutDelegate flowLayoutMarginBetweenItems:self];
        } else {
            margin = 10;  //外界不传高度进来的话  默认间距是100
        }
        return margin;
    }
    #pragma mark - 设置内边距
    - (UIEdgeInsets)sectionInsetOfFlowLayout {
        UIEdgeInsets insets;
        
        if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayoutSectionInsetOfItems:)]) {
            insets = [self.flowLayoutDelegate flowLayoutSectionInsetOfItems:self];
            
            if (insets.left != insets.right) { // 设置左右边距相同(去左右边距较小值)
                CGFloat min = insets.left < insets.right ? insets.left : insets.right;
                
                insets.left = insets.right = min;
            }
        } else {
            insets = UIEdgeInsetsMake(10, 10, 10, 10);
        }
        
        return insets;
    }
    通过下面方法,可以获得外部传入的item的高度值
    #pragma mark - 获取 item 高度
    - (CGFloat)heightForItem:(NSIndexPath *)indexPath itemWithWidth:(CGFloat)width {
        
        CGFloat height = 0;
        
        if ([self.flowLayoutDelegate respondsToSelector:@selector(flowLayout:heightForItems:itemWithWidth:)]) {
            height = [self.flowLayoutDelegate flowLayout:self heightForItems:indexPath itemWithWidth:width];
        } else {
            height = 100 + arc4random_uniform(10) * 10; //外界不传高度进来的话  默认随机产生高度
        }
        
        return height;
    }
    定义一个数组,用来保存最小的Y值,数组的长度依据瀑布流的列数来决定,初始化数组的时候数组的每个元素都赋值为 @0
    
    - (NSMutableArray *)positionArray {
        if (!_positionArray) {
            _positionArray = [NSMutableArray array];
            
            for (int i = 0; i < kColumnCount; i++) {
                _positionArray[i] = @0;
            }
        }
        return _positionArray;
    }
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
        
        CGFloat itemW = (self.collectionView.frame.size.width - kMargin * (kColumnCount - 1) - 2 * kSectionInset.left) / kColumnCount;
        
        //假设数组中第一个就是最小 Y 值
        CGFloat minY = [self.positionArray[0] floatValue];
        
        //设置最小 Y 值的 下标index
        NSInteger indexMinY = 0;
        
        for (int i = 0; i < kColumnCount; i++) {
            CGFloat currentY = [self.positionArray[i] floatValue];
            
            if (minY >= currentY) {
                minY = currentY;
                indexMinY = i;
            }
        }
        
        CGFloat itemX = kSectionInset.left + (kMargin + itemW) * indexMinY;
        
        CGFloat itemH = [self heightForItem:indexPath itemWithWidth:itemW];
        
        CGFloat itemY = minY + kMargin;
        
        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        
        attributes.frame = CGRectMake(itemX, itemY, itemW, itemH);
        
        CGFloat newY = CGRectGetMaxY(attributes.frame);
        
        self.positionArray[indexMinY] = @(newY);
        
        return attributes;
    }

    在上面该方法中,返回的是每一个item的attribute对象,该方法中主要是计算了item得frame值:

        1> 首先,根据item间距 列数 以及内边距的left值来计算出每个item的宽度itemW

        2> 然后假定最小Y值是数组self.positionArray中的第一个值,最小Y值的下标是数组中self.positionArray中的第一个元素的下标,然后遍历self.positionArray数组,来获取真实的最小Y值以及最小Y值的下标.

        3> 根据上面获得的item宽度itemW,最小Y值下标来计算得出

        4> 对于item的高度,则需要调用方法 [self heightForItem:indexPath itemWithWidth:itemW] 如果外部有高度传入就可以获取到传入的高度,如果没有传入则使用默认的高度.

        5> item的Y值就是刚才遍历获得的最小Y值在加上一个间距kMargin

        6> 然后用类UICollectionViewLayoutAttributes来创建一个attribute对象,对其frame进行赋值,并return attribute返回

        7> 在return之前,把刚遍历获得的最小Y值根据最小Y值的下标,把self.positionArray中对应的数据覆盖掉

    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
        
        self.positionArray = nil;
        
        NSMutableArray *attributes = [NSMutableArray array];
        
        //组数
        NSInteger sectionCount = [self.collectionView numberOfSections];
        
        //每组item数
        NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
        
        for (int j = 0; j < sectionCount; j++) {
            for (int i = 0; i < itemCount; i++) {
                
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:j];
                
                UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
                
                [attributes addObject:attribute];
            }
        }
        
        return attributes;
    }

    在上面该方法中:

        1> 在使用该方法时,要注意,要把self.positionArray 置为 空,每次调用该方法时都会去循环调用  layoutAttributesForItemAtIndexPath: 方法,在该方法中要使用到self.positionArray,不置为空的话,数据会出问题.

        2> 通过方法 [self.collectionView numberOfSections] 获取到collectionView得组数.初始化一个可变数组attributes

        3> 循环遍历组数sectionCount:

            3.1> 在该循环中,通过方法 [self.collectionView numberOfItemsInSection:j] 获取到collectionView得每组item数,然后遍历循环每组的item数

            3.2> 再循环中,调用方法 [self layoutAttributesForItemAtIndexPath:indexPath] 获取到每一个item相对应的attribute属性,并添加到可变数组attributes数组中

        4> 循环结束后,return 可变数组attributes 退出方法

    #pragma mark - 设置contentSize
    - (CGSize)collectionViewContentSize {
        
        //定义最大值 Y
        CGFloat maxY = 0;
        
        if (self.positionArray.count) {
            for (int i = 0; i < self.positionArray.count; i++) {
                CGFloat currentY = [self.positionArray[i] floatValue];
                
                if (maxY < currentY) {
                    maxY = currentY;
                }
            }
        }
        return CGSizeMake(0, maxY);
    }

    在该方法中,是为了获取到collectionView的contentSize : 根据数组self.positionArray保存这的数据,可以获取到所有item中的一个最大Y值,然后就可以获取到contentSize

    当items的bounds改变的时候重新布局
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
        return YES;
    }

    通过重写这些方法之后,基本可以实现一个自定义的flowLayout了.

    在collectionView通过这个自定义的flowLayout 来布局,通过给flowLayout设置代理的方式给flowLayout传值,最终实现简单瀑布流的功能实现.

    demo已经上传到了cocoaChina:http://code.cocoachina.com/view/129764

    如果有什么不足之处,请帮忙指点下~~

  • 相关阅读:
    vue中动态数据使用wowjs显示动画
    vue 切换路由页面不在最顶部
    dp,.单词的划分
    二分建火车站
    .最大上升子序列和
    饥饿的奶牛(不重区间最大值)
    F. 1.小W 的质数(prime)(欧拉筛)
    月月给华华出题
    积性函数
    垒石头(排序+dp)
  • 原文地址:https://www.cnblogs.com/qhlbk/p/5246411.html
Copyright © 2011-2022 走看看