美文网首页iOS开发知识小集iOS基础
iOS - 瀑布流简单的实现

iOS - 瀑布流简单的实现

作者: ShIwEn9 | 来源:发表于2019-03-21 21:10 被阅读24次

    前言

    平时没事也喜欢逛一些技术网站,而瀑布流因为其炫酷的视觉效果(其实就是模仿)和样式使其成为ios当中比较火热的一种技术。
    在最近的一个项目中,生活也对我这个菜鸟下了毒手,要求实现瀑布流(WTF。。。)吐槽归吐槽但是效果还是得做出来。。。
    现在我来介绍一下我的实现思路(其实是参考网上案例🤪)

    初步接触UICollectionView的小伙伴们肯定都知道苹果官方为我们封装好的UICollectionViewFlowLayout,在很多情况下我们使用UICollectionViewFlowLayout就可以实现需求了。
    下面的列子简单实用UICollectionViewFlowLayout实现效果

    //设置流水布局
      UICollectionViewFlowLayout *layout = ({
          //设置流水布局
          UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc]init] ;
          layout.itemSize = CGSizeMake(itemWH, itemWH) ;
          layout.minimumInteritemSpacing = margin; // 最小间距
          layout.minimumLineSpacing = margin; //最小行距
          layout ;
      }) ;
      
      //创建CollectionView
      UICollectionView *collocetView = ({
          //创建CollectionView
          UICollectionView *collocetView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 10, kScreenW, 450) collectionViewLayout:layout] ;
          collocetView.backgroundColor= [UIColor whiteColor ];
          //设置数据源和代理
          collocetView.delegate = self ;
          collocetView.dataSource = self ;
          collocetView.alwaysBounceHorizontal = YES;
          
          self.collectionView = collocetView ;
          
          //注册cell 一定要做
          [collocetView registerClass:[SWCollectionViewCell class] forCellWithReuseIdentifier:itemID] ;
          collocetView ;
      });
    
    
     //注册头部
        [self.collectionView registerClass:[SWCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:heardID] ;
        
        //注册尾部
        [self.collectionView registerClass:[SWCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:heardID];
    
    这里得强调一下UICollectionView的cell和头尾试图通常都是注册实现的。。。。

    二. 瀑布流的实现思路:

    1. 控件选择:现在实现瀑布流主流的思路是用UICollectionView来实现,优点不言而喻,因为其丰富的API和类似tableView的复用机制等 。。。可能也有人用srollView实现(我不会,,,唉~)。

    2. 因为官方的UICollectionViewFlowLayout是继承自UICollectionViewLayout,所以我们也可以参照官方的实现瀑布流效果

    3. 现在我们需要指定滚动方向、默认列数、行间距、列间距、以及指定cell的宽度itemWidth还需要通过方法计算高度

    import "SWWaterFallLayout.h"

    @class SWWaterFallLayout ;
    //1.定义协议
    @protocol SWWaterFallLayoutDelegate<NSObject>
    //必须实现的方法
    @required
    /**
     每一列的宽度
     */
    - (CGFloat)waterFallLayout:(SWWaterFallLayout *)waterFallLayout heightForItemAtIndexPath:(NSUInteger)indexPath itemWidth:(CGFloat)itemWidth ;
    
    //可选
    @optional
    /**
     一共有多少列
     */
    -(NSInteger)columnCountWaterFallLayout:(SWWaterFallLayout *)waterFallLayout ;
    /**
     每一列的间距
     */
    -(CGFloat)columnMarginWaterFallLayout:(SWWaterFallLayout *)waterFallLayout ;
    /**
     每一行的间距
     */
    -(CGFloat)rowMarginWaterFallLayout:(SWWaterFallLayout *)waterFallLayout ;
    /**
     每一个Item的内间距
     */
    -(UIEdgeInsets)edgeInsetsInWaterFallLayout:(SWWaterFallLayout *)waterFallLayout ;
    
    @end
    @interface SWWaterFallLayout : UICollectionViewLayout
    
    - (NSUInteger)colunmCount;
    - (CGFloat)columnMargin;
    - (CGFloat)rowMargin;
    - (UIEdgeInsets)edgeInsets;
    
    //2.声明代理属性
    @property(nonatomic,weak) id<SWWaterFallLayoutDelegate> delegate ;
    
    1. 可以提供一个数组attrsArr存放所有的布局属性,columnHeights存放所有列的当前高度

    import "SWWaterFallLayout.h"

    /** 默认的列数    */
    static const CGFloat SWDefaultColunmCount = 3;
    
    /** 每一列之间的间距    */
    static const CGFloat SWDefaultColunmMargin = 10;
    
    /** 每一行之间的间距    */
    static const CGFloat SWDefaultRowMargin = 10;
    
    /** 内边距    */
    static const UIEdgeInsets SWDefaultEdgeInsets = {10,10,10,10};
    
    @interface SWWaterFallLayout ()
    /** 存放所有的布局属性 */
    @property (nonatomic, strong) NSMutableArray * attrsArr;
    /** 存放所有列的当前高度 */
    @property (nonatomic, strong) NSMutableArray *columnHeights;
    /** 内容的高度 */
    @property (nonatomic, assign) CGFloat contentHeight;
    
    @end
    
    @implementation SWWaterFallLayout
    
    - (NSMutableArray *)attrsArr{
        if (!_attrsArr) {
            _attrsArr = [NSMutableArray array];
        }
        return _attrsArr;
    }
    
    - (NSMutableArray *)columnHeights{
        if (!_columnHeights) {
            _columnHeights = [NSMutableArray array];
        }
        
        return _columnHeights;
    }
    
    #pragma mark - 数据处理
    /**
     * 列数
     */
    -(NSUInteger)colunmCount{
        if ([self.delegate respondsToSelector:@selector(columnCountWaterFallLayout:)]) {
            return [self.delegate columnCountWaterFallLayout:self] ;
        }else{
            return SWDefaultColunmCount ;
        }
    }
    
    /**
     * 行间距
     */
    - (CGFloat)rowMargin{
        if ([self.delegate respondsToSelector:@selector(rowMarginWaterFallLayout:)]) {
            return [self.delegate rowMarginWaterFallLayout:self];
        }else{
            return SWDefaultRowMargin;
        }
    }
    
    /**
     * 列间距
     */
    - (CGFloat)columnMargin{
        if ([self.delegate respondsToSelector:@selector(columnMarginWaterFallLayout:)]) {
            return [self.delegate columnMarginWaterFallLayout:self];
        }else{
            return SWDefaultColunmMargin;
        }
    }
    
    /**
     * item的内边距
     */
    - (UIEdgeInsets)edgeInsets{
        if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterFallLayout:)]) {
            return [self.delegate edgeInsetsInWaterFallLayout:self];
        }else{
            return SWDefaultEdgeInsets;
        }
    }
    
    /**
     * 初始化
     */
    - (void)prepareLayout{
        
        [super prepareLayout];
        //内容的高度
        self.contentHeight = 0;
        
        // 清除之前计算的所有高度
        [self.columnHeights removeAllObjects];
        
        // 设置每一列默认的高度
        for (NSInteger i = 0; i < SWDefaultColunmCount ; i ++) {
            [self.columnHeights addObject:@(SWDefaultEdgeInsets.top)];
        }
        // 清楚之前所有的布局属性
        [self.attrsArr removeAllObjects];
        
        // 开始创建每一个cell对应的布局属性
        NSInteger count = [self.collectionView numberOfItemsInSection:0] ;
        
        for (int i = 0; i < count; i++) {
            // 创建位置
            NSIndexPath * indexPath = [NSIndexPath indexPathForItem:i inSection:0] ;
            
            // 获取indexPath位置上cell对应的布局属性
            UICollectionViewLayoutAttributes * attrs = [self layoutAttributesForItemAtIndexPath:indexPath] ;
            
            [self.attrsArr addObject:attrs] ;
        }
    }
    
    /**
     * 返回indexPath位置cell对应的布局属性
     Attributes :属性
     */
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
        // 创建布局属性
        UICollectionViewLayoutAttributes * attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        
        //collectionView的宽度
        CGFloat collectionViewW = self.collectionView.frame.size.width;
        // 设置布局属性的frame
        CGFloat cellW = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.colunmCount - 1) * self.columnMargin) / self.colunmCount ;
        
        CGFloat cellH = [self.delegate waterFallLayout:self heightForItemAtIndexPath:indexPath.item itemWidth:cellW];
        // 找出最短的那一列
        NSInteger destColumn = 0;
        CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
        
        for (int i = 1; i < SWDefaultColunmCount; i++) {
            
            // 取得第i列的高度
            CGFloat columnHeight = [self.columnHeights[i] doubleValue];
            //判断是否替换最小的列宽
            if (minColumnHeight > columnHeight) {
                minColumnHeight = columnHeight;
                destColumn = I;
            }
        }
        CGFloat cellX = self.edgeInsets.left + destColumn * (cellW + self.columnMargin);
        CGFloat cellY = minColumnHeight;
        if (cellY != self.edgeInsets.top) {
            
            cellY += self.rowMargin;
        }
        attrs.frame = CGRectMake(cellX, cellY, cellW, cellH);
        
        // 更新最短那一列的高度
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        
        // 记录内容的高度 - 即最长那一列的高度
        CGFloat maxColumnHeight = [self.columnHeights[destColumn] doubleValue];
        if (self.contentHeight < maxColumnHeight) {
            self.contentHeight = maxColumnHeight;
        }
        
        return attrs;
    }
    
    /**
     * 决定cell的高度
     */
    - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
        
        return self.attrsArr;
    }
    
    /**
     * 内容的高度
     */
    - (CGSize)collectionViewContentSize{
        
            CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
            for (int i = 0; i < SWDefaultColunmCount; i++) {
        
                // 取得第i列的高度
                CGFloat columnHeight = [self.columnHeights[i] doubleValue];
        
                if (maxColumnHeight < columnHeight) {
                    maxColumnHeight = columnHeight;
                }
        
            }
        
        return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
    }
    
    @end
    

    最后在控制器中实现将collectView的layout设置成自定义的layout其他不变,就行啦。。。。

    瀑布流

    吐槽:加班到爆炸啦~💥

    相关文章

      网友评论

        本文标题:iOS - 瀑布流简单的实现

        本文链接:https://www.haomeiwen.com/subject/rckhvqtx.html