美文网首页
瀑布流(Swift版和OC版)

瀑布流(Swift版和OC版)

作者: 只敲代码不偷桃 | 来源:发表于2016-09-01 15:06 被阅读92次

    原理分析 :

    啥叫瀑布流?就是每次加载出来的cell(或者叫item)都是放到最短的那行,像瀑布流水一样出现,其关键思想就是如何计算出每个将要出现的cell的frame。即x、y、w、h。那咱就从浅入深、顺藤摸瓜的开始分析:
    首先最好算的是cell的宽w,显然,根据列数、列与列之间的间距、以及距离屏幕边缘两侧即可算出。即:
    w = 屏幕宽度 - 距屏幕两侧的间距 - (列数-1)* 列间距。
    第二步:然后再看x。通过分析可以知道,其实只要知道了cell当前的列数,就可算出x。即:
    x = 剧屏幕左侧的距离 + 当前列数 * (cell的宽(也就是w)加上列间距)
    第三步:紧接着可以分析出,如果知道了某个cell的x,那肯定能知道此cell的y值,因为显然当前cell的y肯定就是当前列的高度(即最大y值),加上行间距即可。
    这么分析下来,则重中之重,就是要找到最短的那一列,找到了,一切问题就迎刃而解了。
    可以定义一个数组,用来存放列的高度值,再定义第0列是最短的那一列,遍历存放高度的数组,比较得出最小列的列数。然后得到了最小列的列数,则可以通过列的下标计算出x值,进而算出y值。即:
    x = 剧屏幕左侧的距离 + 高度最小列的列数 * (cell的宽(也就是w)加上列间距)
    y = 高度最小的高度值 + 行间距
    算完y值之后,记得更新最短那列的高度值
    高度h需要外界来确定,列数也是外界决定,更好运用疯转思想,高内聚、低耦合。

    下面直接上轮子:

    Swift版

    协议

    //Swift中定义协议: 必须遵守NSObjectProtocol
    @objc protocol WaterfallsLayoutDelegate{
        ///返回每个item的宽高 必须实现
        func waterflowLayout(waterflowLayout:LZYWaterfallsLayout, itemIndex: Int, itemWidth:CGFloat) -> CGFloat
    
        ///返回列数 (非必须)
       optional func columnCountInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> Int
        
        ///返回列间距 (非必须)
        optional func columnMarginInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> CGFloat
        
        ///返回行间距 (非必须)
        optional func rowMarginInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> CGFloat
        
        ///返回边缘间距 上 左 下 右 (非必须)
        optional func edgeInsetsInWaterflowLayout(waterflowLayout:LZYWaterfallsLayout) -> UIEdgeInsets   
    }
    

    自定义的布局类

    class LZYWaterfallsLayout: UICollectionViewLayout {
         ///定义一个属性保存代理对象 一定要加上weak, 避免循环引用
        weak var delegate: WaterfallsLayoutDelegate?
        /// 总列数
        var columnCount : Int {
            return delegate?.columnCountInWaterflowLayout?(self) ?? 2
        }
        /// 列间距
        var columnMargin: CGFloat {
            return delegate?.columnMarginInWaterflowLayout?(self) ?? 10
        }
        /// 行间距
        var rowMargin: CGFloat {
            return delegate?.rowMarginInWaterflowLayout?(self) ?? 10
        }
        /// 当前内边距
        var currentEdgeInsets: UIEdgeInsets {
            return delegate?.edgeInsetsInWaterflowLayout?(self) ?? UIEdgeInsets(top: 10,left: 10,bottom: 10,right: 10) //内边距
        }
        /// 布局属性数组
        private var attrsArray = [UICollectionViewLayoutAttributes]()
        /// 存放所有列的当前高度
        private var columnHeights = [CGFloat]()
        /// 内容的高度
        private var contentHeight:CGFloat = 0
        ///MARK: - 重写系统方法
        override func prepareLayout() {
            super.prepareLayout()
            // 清除以前计算的所有高度
            columnHeights.removeAll()
            
            for _ in 0..<columnCount {
                columnHeights.append(currentEdgeInsets.top)
            }
            // 清除之前所有的布局属性
            attrsArray.removeAll()
            // 开始创建每一个cell对应的布局属性
            let count = collectionView!.numberOfItemsInSection(0)
            for i in 0..<count {
                let indexPath = NSIndexPath(forItem: i, inSection: 0)
                // 获取indexPath位置cell对应的布局属性
                let attrs = layoutAttributesForItemAtIndexPath(indexPath)
                attrsArray.append(attrs!)
            }
        }
        ///决定cell的排布
        override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            return attrsArray
        }
        ///返回indexPath位置cell对应的布局属性
        override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
            let attrs = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
            let collectionViewW = collectionView?.frame.size.width
            // 设置布局属性的frame
            let w = (collectionViewW! - currentEdgeInsets.left - currentEdgeInsets.right - CGFloat(columnCount - 1) * columnMargin) / CGFloat(columnCount)
            let h = delegate?.waterflowLayout(self, itemIndex: indexPath.item, itemWidth: w)
            // 找出高度最短的那一列
            var destColumn = 0
            var minColumnHeight = columnHeights[0]
            for i in 1..<columnCount {
                // 取得第i列的高度
                let columnHeight = columnHeights[i]
                if minColumnHeight > columnHeight {
                    minColumnHeight = columnHeight
                    destColumn = i
                }
            }
            
            let x = currentEdgeInsets.left + CGFloat(destColumn) * (w + columnMargin)
            var y = minColumnHeight
            if y != currentEdgeInsets.top {
                y += rowMargin
            }
            attrs.frame = CGRectMake(x, y, w, h!)
            
            // 更新最短那列的高度
            columnHeights[destColumn] = CGRectGetMaxY(attrs.frame)
            
            // 记录内容的高度
            let columnHeight = columnHeights[destColumn]
            if contentHeight < columnHeight {
                contentHeight = columnHeight
            }
            return attrs
        }
        
        ///collectionView的ContentSize
        override func collectionViewContentSize() -> CGSize {
            return CGSizeMake(0, contentHeight + currentEdgeInsets.bottom)
        }
    }
    

    OC版

    在.h文件中

    //  FZXWaterfallsLayout.h
    //
    //  功用: 瀑布流的布局
    
    #import <UIKit/UIKit.h>
    
    @class FZXWaterfallsLayout;
    
    // 2.构造方法,使得外界通过重写代理方面,控制细节
    @protocol FZXWaterfallsLayoutDelegate <NSObject>
    
    @required
    /**
     *  返回每个item的宽高 必须实现
     */
    - (CGFloat)waterflowLayout:(FZXWaterfallsLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;
    
    //可选实现
    @optional
    /**
     *  返回列数
     */
    - (CGFloat)columnCountInWaterflowLayout:(FZXWaterfallsLayout *)waterflowLayout;
    /**
     *  返回列间距
     */
    - (CGFloat)columnMarginInWaterflowLayout:(FZXWaterfallsLayout *)waterflowLayout;
    /**
     *  返回行间距
     */
    - (CGFloat)rowMarginInWaterflowLayout:(FZXWaterfallsLayout *)waterflowLayout;
    /**
     *  返回边缘间距 上 左 下 右
     */
    - (UIEdgeInsets)edgeInsetsInWaterflowLayout:(FZXWaterfallsLayout *)waterfallLayout;
    
    @end
    
    @interface FZXWaterfallsLayout : UICollectionViewLayout
    
    /**
     *  设置代理,使得外界使用代理方面控制内部显示细节
     */
    @property (nonatomic, weak) id<FZXWaterfallsLayoutDelegate> delegate;
    
    @end
    

    在.m文件中

    //  FZXWaterfallsLayout.m
    //
    //  Created by FZX on 16/5/20.
    //
    //  功用: 瀑布流的布局
    
    #import "FZXWaterfallsLayout.h"
    
    /** 默认的列数 */
    static const NSInteger FZXDefaultColumnCount = 2;
    /** 每一列之间的间距 */
    static const CGFloat FZXDefaultColumnMargin = 10;
    /** 每一行之间的间距 */
    static const CGFloat FZXDefaultRowMargin = 10;
    /** 边缘间距 */
    static const UIEdgeInsets FZXDefaultEdgeInsets = {10, 10, 10, 10};
    
    @interface FZXWaterfallsLayout ()
    /** 存放所有cell的布局属性 */
    @property (nonatomic, strong) NSMutableArray *attrsArray;
    /** 存放所有列的当前高度 */
    @property (nonatomic, strong) NSMutableArray *columnHeights;
    /** 内容的高度 */
    @property (nonatomic, assign) CGFloat contentHeight;
    
    // 提供和重写get方法,实现对代理方法的实时监控
    - (CGFloat)rowMargin;
    - (CGFloat)columnMargin;
    - (NSInteger)columnCount;
    - (UIEdgeInsets)edgeInsets;
    
    @end
    
    @implementation FZXWaterfallsLayout
    
    #pragma mark - 初始化
    - (void)prepareLayout
    {
        [super prepareLayout];
        
        self.contentHeight = 0;
        
        // 清除以前计算的所有高度
        [self.columnHeights removeAllObjects];
        
        for (NSInteger i = 0; i < self.columnCount; i++)
        {
            [self.columnHeights addObject:@(self.edgeInsets.top)];
        }
        
        
        // 清除之前所有的布局属性
        [self.attrsArray removeAllObjects];
        // 开始创建每一个cell对应的布局属性
        NSInteger count = [self.collectionView numberOfItemsInSection:0];
        for (NSInteger i = 0; i < count; i++)
        {
            // 创建位置
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
            // 获取indexPath位置cell对应的布局属性
            UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
            [self.attrsArray addObject:attrs];
        }
    }
    
    #pragma mark - 决定cell的排布
    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        return self.attrsArray;
    }
    
    #pragma mark - 返回indexPath位置cell对应的布局属性
    - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        // 创建布局属性
        UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        
        // collectionView的宽度
        CGFloat collectionViewW = self.collectionView.frame.size.width;
        
        // 设置布局属性的frame
        CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
        CGFloat h = [self.delegate waterflowLayout:self heightForItemAtIndex:indexPath.item itemWidth:w];
        
        // 找出高度最短的那一列
        NSInteger destColumn = 0;
        CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
        for (NSInteger i = 1; i < self.columnCount; i++) {
            // 取得第i列的高度
            CGFloat columnHeight = [self.columnHeights[i] doubleValue];
            
            if (minColumnHeight > columnHeight) {
                minColumnHeight = columnHeight;
                destColumn = i;
            }
        }
        
        CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
        CGFloat y = minColumnHeight;
        if (y != self.edgeInsets.top) {
            y += self.rowMargin;
        }
        attrs.frame = CGRectMake(x, y, w, h);
        
        // 更新最短那列的高度
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
        
        // 记录内容的高度
        CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
        if (self.contentHeight < columnHeight) {
            self.contentHeight = columnHeight;
        }
        return attrs;
    }
    
    #pragma mark - collectionView的ContentSize
    - (CGSize)collectionViewContentSize
    {
        return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
    }
    
    #pragma mark - 设置默认的一些属性
    //列数
    -(NSInteger)columnCount
    {
        if ([self.delegate respondsToSelector:@selector(columnCountInWaterflowLayout:)])
        {
            return [self.delegate columnCountInWaterflowLayout:self];
        }
        else
        {
            return FZXDefaultColumnCount;
        }
    }
    //每一列的间距
    -(CGFloat)columnMargin
    {
        if ([self.delegate respondsToSelector:@selector(columnMarginInWaterflowLayout:)])
        {
            return [self.delegate columnMarginInWaterflowLayout:self];
        }
        else
        {
            return FZXDefaultColumnMargin;
        }
    }
    //行间距
    -(CGFloat)rowMargin
    {
        if ([self.delegate respondsToSelector:@selector(rowMarginInWaterflowLayout:)])
        {
            return [self.delegate rowMarginInWaterflowLayout:self];
        }
        else
        {
            return FZXDefaultRowMargin;
        }
    }
    //边缘
    -(UIEdgeInsets)edgeInsets
    {
        if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterflowLayout:)])
        {
            return [self.delegate edgeInsetsInWaterflowLayout:self];
        }
        else
        {
            return FZXDefaultEdgeInsets;
        }
    }
    
    #pragma mark - 懒加载
    -(NSMutableArray *)attrsArray
    {
        if (!_attrsArray) {
            _attrsArray = [NSMutableArray array];
        }
        return _attrsArray;
    }
    
    -(NSMutableArray *)columnHeights
    {
        if (!_columnHeights) {
            _columnHeights = [NSMutableArray array];
        }
        return _columnHeights;
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:瀑布流(Swift版和OC版)

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